@scrabble-solver/scrabble-solver 2.10.8 → 2.10.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 +15 -15
- 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/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-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/images-manifest.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/131.js +24 -17
- package/.next/server/chunks/176.js +443 -288
- package/.next/server/chunks/50.js +712 -1025
- package/.next/server/chunks/664.js +27 -2414
- package/.next/server/chunks/859.js +29 -1
- package/.next/server/font-loader-manifest.js +1 -0
- package/.next/server/font-loader-manifest.json +6 -0
- 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 +1 -12
- package/.next/server/pages/_app.js +13 -133
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +133 -17
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +5 -6
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +34 -154
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/main-74c4d6b2b5c362f3.js +1 -0
- package/.next/static/chunks/pages/{404-8cab6d62fe4ead73.js → 404-d5ff00df1c687977.js} +1 -1
- package/.next/static/chunks/pages/_app-3272e798504c40d8.js +28 -0
- package/.next/static/chunks/pages/index-5c2544930e46c5ce.js +1 -0
- package/.next/static/chunks/webpack-6ef43a8d4a395f49.js +1 -0
- package/.next/static/css/336e75db2b74b157.css +2 -0
- package/.next/static/css/{d1cc6b79b211b7b8.css → ec4e47a6b1866fe5.css} +1 -1
- package/.next/static/warzWo25tDxo_Eiv9T6f2/_buildManifest.js +1 -0
- package/.next/trace +55 -55
- package/package.json +12 -11
- package/src/components/Board/Board.module.scss +14 -0
- package/src/components/Board/Board.tsx +117 -19
- package/src/components/Board/BoardPure.tsx +7 -15
- package/src/components/Board/components/Actions/Actions.module.scss +64 -0
- package/src/components/Board/components/Actions/Actions.tsx +74 -0
- package/src/components/Board/components/Actions/index.ts +1 -0
- package/src/components/Board/components/Cell/Cell.module.scss +10 -121
- package/src/components/Board/components/Cell/Cell.tsx +0 -37
- package/src/components/Board/components/Cell/CellPure.tsx +5 -68
- package/src/components/Board/components/index.ts +1 -0
- package/src/components/Board/hooks/useGrid.ts +16 -16
- package/src/components/Modal/Modal.module.scss +3 -1
- package/src/components/Rack/Rack.module.scss +2 -1
- package/src/components/Solver/Solver.module.scss +0 -5
- package/src/components/Solver/Solver.tsx +1 -1
- package/src/components/Tile/Tile.module.scss +0 -26
- package/src/components/Tile/Tile.tsx +5 -3
- package/src/components/Tile/TilePure.tsx +16 -18
- package/src/hooks/useLocalStorage/useLocalStorageBoard.ts +6 -3
- package/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +6 -3
- package/src/hooks/useLocalStorage/useLocalStorageLocale.ts +6 -3
- package/src/hooks/useLocalStorage/useLocalStorageRack.ts +6 -3
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +1 -1
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +2 -2
- package/src/parameters/index.ts +5 -3
- package/src/styles/animations.scss +10 -0
- package/src/styles/global.scss +2 -2
- package/src/styles/mixins.scss +56 -1
- package/src/styles/variables.scss +5 -2
- package/.next/server/chunks/210.js +0 -122
- package/.next/server/chunks/676.js +0 -32
- package/.next/static/Cs23uxWG6AxS72F2yrjHu/_buildManifest.js +0 -1
- package/.next/static/chunks/main-f11614d8aa7ee555.js +0 -1
- package/.next/static/chunks/pages/_app-dcbbb823dc93a031.js +0 -28
- package/.next/static/chunks/pages/index-df1ff01aa82d2d4d.js +0 -1
- package/.next/static/chunks/webpack-59c5c889f52620d6.js +0 -1
- package/.next/static/css/bf2e969b88c4e3dd.css +0 -2
- /package/.next/static/{Cs23uxWG6AxS72F2yrjHu → warzWo25tDxo_Eiv9T6f2}/_ssgManifest.js +0 -0
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { Bonus, Cell, Tile as TileModel } from '@scrabble-solver/types';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import {
|
|
4
|
-
ChangeEventHandler,
|
|
5
|
-
CSSProperties,
|
|
6
|
-
FocusEventHandler,
|
|
7
|
-
FunctionComponent,
|
|
8
|
-
memo,
|
|
9
|
-
MouseEventHandler,
|
|
10
|
-
RefObject,
|
|
11
|
-
} from 'react';
|
|
3
|
+
import { ChangeEventHandler, CSSProperties, FocusEventHandler, FunctionComponent, memo, RefObject } from 'react';
|
|
12
4
|
|
|
13
|
-
import {
|
|
14
|
-
import { Translate } from 'types';
|
|
5
|
+
import { FlagFill, Star } from 'icons';
|
|
15
6
|
|
|
16
|
-
import Button from '../../../Button';
|
|
17
7
|
import Tile from '../../../Tile';
|
|
18
8
|
|
|
19
9
|
import styles from './Cell.module.scss';
|
|
@@ -24,7 +14,6 @@ interface Props {
|
|
|
24
14
|
bonus: Bonus | undefined;
|
|
25
15
|
cell: Cell;
|
|
26
16
|
className?: string;
|
|
27
|
-
direction: 'horizontal' | 'vertical';
|
|
28
17
|
inputRef: RefObject<HTMLInputElement>;
|
|
29
18
|
isBottom: boolean;
|
|
30
19
|
isCenter: boolean;
|
|
@@ -36,12 +25,8 @@ interface Props {
|
|
|
36
25
|
size: number;
|
|
37
26
|
style?: CSSProperties;
|
|
38
27
|
tile: TileModel;
|
|
39
|
-
translate: Translate;
|
|
40
28
|
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
41
|
-
onDirectionToggleClick: MouseEventHandler<HTMLButtonElement>;
|
|
42
29
|
onFocus: FocusEventHandler<HTMLInputElement>;
|
|
43
|
-
onToggleBlankClick: MouseEventHandler<HTMLButtonElement>;
|
|
44
|
-
onToggleFilterCellClick: MouseEventHandler<HTMLButtonElement>;
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
const CellPure: FunctionComponent<Props> = ({
|
|
@@ -49,7 +34,6 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
49
34
|
bonus,
|
|
50
35
|
cell,
|
|
51
36
|
className,
|
|
52
|
-
direction,
|
|
53
37
|
inputRef,
|
|
54
38
|
isBottom,
|
|
55
39
|
isCenter,
|
|
@@ -61,32 +45,20 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
61
45
|
size,
|
|
62
46
|
style,
|
|
63
47
|
tile,
|
|
64
|
-
translate,
|
|
65
48
|
onChange,
|
|
66
|
-
onDirectionToggleClick,
|
|
67
49
|
onFocus,
|
|
68
|
-
onToggleBlankClick,
|
|
69
|
-
onToggleFilterCellClick,
|
|
70
50
|
}) => (
|
|
71
51
|
<div
|
|
72
52
|
className={classNames(styles.cell, className, getBonusClassname(cell, bonus, isCenter), {
|
|
73
53
|
[styles.bottom]: isBottom,
|
|
74
|
-
[styles.
|
|
54
|
+
[styles.filtered]: isFiltered,
|
|
75
55
|
[styles.right]: isRight,
|
|
76
56
|
})}
|
|
77
57
|
style={style}
|
|
78
58
|
>
|
|
79
|
-
{isCenter && isEmpty &&
|
|
80
|
-
<div className={classNames(styles.iconContainer)}>
|
|
81
|
-
<Star className={styles.star} />
|
|
82
|
-
</div>
|
|
83
|
-
)}
|
|
59
|
+
{isCenter && isEmpty && !isFiltered && <Star className={styles.icon} />}
|
|
84
60
|
|
|
85
|
-
{isFiltered &&
|
|
86
|
-
<div className={classNames(styles.iconContainer, styles.flagContainer)}>
|
|
87
|
-
<FlagFill className={styles.flag} />
|
|
88
|
-
</div>
|
|
89
|
-
)}
|
|
61
|
+
{isFiltered && <FlagFill className={styles.icon} />}
|
|
90
62
|
|
|
91
63
|
<Tile
|
|
92
64
|
aria-label={ariaLabel}
|
|
@@ -103,41 +75,6 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
103
75
|
onChange={onChange}
|
|
104
76
|
onFocus={onFocus}
|
|
105
77
|
/>
|
|
106
|
-
|
|
107
|
-
{!cell.isCandidate() && (
|
|
108
|
-
<div className={styles.actions}>
|
|
109
|
-
<Button
|
|
110
|
-
aria-label={translate('cell.toggle-direction')}
|
|
111
|
-
className={styles.action}
|
|
112
|
-
Icon={ArrowDown}
|
|
113
|
-
iconClassName={classNames(styles.toggleDirection, {
|
|
114
|
-
[styles.right]: direction === 'horizontal',
|
|
115
|
-
})}
|
|
116
|
-
tooltip={translate('cell.toggle-direction')}
|
|
117
|
-
onClick={onDirectionToggleClick}
|
|
118
|
-
/>
|
|
119
|
-
|
|
120
|
-
{isEmpty && (
|
|
121
|
-
<Button
|
|
122
|
-
aria-label={translate('cell.filter-cell')}
|
|
123
|
-
className={classNames(styles.action)}
|
|
124
|
-
Icon={isFiltered ? Flag : FlagFill}
|
|
125
|
-
tooltip={translate('cell.filter-cell')}
|
|
126
|
-
onClick={onToggleFilterCellClick}
|
|
127
|
-
/>
|
|
128
|
-
)}
|
|
129
|
-
|
|
130
|
-
{!isEmpty && (
|
|
131
|
-
<Button
|
|
132
|
-
aria-label={tile.isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
|
|
133
|
-
className={styles.action}
|
|
134
|
-
Icon={tile.isBlank ? SquareFill : Square}
|
|
135
|
-
tooltip={tile.isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
|
|
136
|
-
onClick={onToggleBlankClick}
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
139
|
-
</div>
|
|
140
|
-
)}
|
|
141
78
|
</div>
|
|
142
79
|
);
|
|
143
80
|
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
|
|
3
3
|
import { Board, Cell } from '@scrabble-solver/types';
|
|
4
4
|
import {
|
|
5
|
+
ChangeEvent,
|
|
6
|
+
ChangeEventHandler,
|
|
7
|
+
ClipboardEventHandler,
|
|
5
8
|
createRef,
|
|
6
9
|
KeyboardEventHandler,
|
|
7
10
|
RefObject,
|
|
8
11
|
useCallback,
|
|
9
12
|
useMemo,
|
|
10
13
|
useState,
|
|
11
|
-
useRef,
|
|
12
|
-
ChangeEventHandler,
|
|
13
|
-
ChangeEvent,
|
|
14
|
-
ClipboardEventHandler,
|
|
15
14
|
} from 'react';
|
|
16
15
|
import { useDispatch } from 'react-redux';
|
|
17
16
|
import { useLatest } from 'react-use';
|
|
@@ -28,8 +27,9 @@ import { Point } from '../types';
|
|
|
28
27
|
const toggleDirection = (direction: Direction) => (direction === 'vertical' ? 'horizontal' : 'vertical');
|
|
29
28
|
|
|
30
29
|
interface State {
|
|
30
|
+
activeIndex: Point;
|
|
31
31
|
direction: Direction;
|
|
32
|
-
|
|
32
|
+
inputRefs: RefObject<HTMLInputElement>[][];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
interface Actions {
|
|
@@ -46,29 +46,29 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
46
46
|
const dispatch = useDispatch();
|
|
47
47
|
const config = useTypedSelector(selectConfig);
|
|
48
48
|
const locale = useTypedSelector(selectLocale);
|
|
49
|
-
const
|
|
49
|
+
const inputRefs = useMemo(
|
|
50
50
|
() => createGridOf<RefObject<HTMLInputElement>>(width, height, () => createRef()),
|
|
51
51
|
[width, height],
|
|
52
52
|
);
|
|
53
|
-
const
|
|
53
|
+
const [activeIndex, setActiveIndex] = useState<Point>({ x: 0, y: 0 });
|
|
54
54
|
const [direction, setLastDirection] = useState<Direction>('horizontal');
|
|
55
55
|
const directionRef = useLatest(direction);
|
|
56
56
|
|
|
57
57
|
const changeActiveIndex = useCallback(
|
|
58
58
|
(offsetX: number, offsetY: number) => {
|
|
59
|
-
const x = Math.min(Math.max(
|
|
60
|
-
const y = Math.min(Math.max(
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const x = Math.min(Math.max(activeIndex.x + offsetX, 0), width - 1);
|
|
60
|
+
const y = Math.min(Math.max(activeIndex.y + offsetY, 0), height - 1);
|
|
61
|
+
setActiveIndex({ x, y });
|
|
62
|
+
inputRefs[y][x].current?.focus();
|
|
63
63
|
},
|
|
64
|
-
[
|
|
64
|
+
[activeIndex, inputRefs],
|
|
65
65
|
);
|
|
66
66
|
|
|
67
67
|
const getInputRefPosition = useCallback(
|
|
68
68
|
(inputRef: HTMLInputElement): Point | undefined => {
|
|
69
|
-
return getPositionInGrid(
|
|
69
|
+
return getPositionInGrid(inputRefs, (ref) => ref.current === inputRef);
|
|
70
70
|
},
|
|
71
|
-
[
|
|
71
|
+
[inputRefs],
|
|
72
72
|
);
|
|
73
73
|
|
|
74
74
|
const moveFocus = useCallback(
|
|
@@ -217,7 +217,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
217
217
|
const onDirectionToggle = useCallback(() => setLastDirection(toggleDirection), []);
|
|
218
218
|
|
|
219
219
|
const onFocus = useCallback((x: number, y: number) => {
|
|
220
|
-
|
|
220
|
+
setActiveIndex({ x, y });
|
|
221
221
|
}, []);
|
|
222
222
|
|
|
223
223
|
const onKeyDown = useMemo(() => {
|
|
@@ -356,7 +356,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
356
356
|
);
|
|
357
357
|
|
|
358
358
|
return [
|
|
359
|
-
{ direction,
|
|
359
|
+
{ activeIndex, direction, inputRefs },
|
|
360
360
|
{ onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
|
|
361
361
|
];
|
|
362
362
|
};
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
.modal {
|
|
17
17
|
position: fixed;
|
|
18
|
+
bottom: 0;
|
|
18
19
|
z-index: var(--z-index--modal);
|
|
19
|
-
height: 100vh;
|
|
20
20
|
width: var(--modal--width);
|
|
21
21
|
padding: 0;
|
|
22
22
|
border-radius: 0;
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
background-color: var(--color--background);
|
|
70
70
|
transition: var(--transition--long);
|
|
71
71
|
transform: translateX(var(--modal--width));
|
|
72
|
+
overflow: hidden;
|
|
72
73
|
opacity: 0;
|
|
73
74
|
|
|
74
75
|
[dir='ltr'] & {
|
|
@@ -113,6 +114,7 @@
|
|
|
113
114
|
.content {
|
|
114
115
|
position: relative;
|
|
115
116
|
flex: 1;
|
|
117
|
+
min-height: 0;
|
|
116
118
|
margin-top: calc(-1 * var(--spacing--l));
|
|
117
119
|
padding: var(--spacing--l);
|
|
118
120
|
overflow: auto;
|
|
@@ -157,7 +157,7 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
157
157
|
|
|
158
158
|
<div className={styles.bottomContainer} ref={bottomContainerRef}>
|
|
159
159
|
<div className={styles.bottomContent}>
|
|
160
|
-
<form
|
|
160
|
+
<form onSubmit={handleSubmit}>
|
|
161
161
|
<Rack className={styles.rack} tileSize={tileSize} />
|
|
162
162
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
163
163
|
</form>
|
|
@@ -54,16 +54,6 @@
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
.inputContainer {
|
|
58
|
-
position: absolute;
|
|
59
|
-
top: 0;
|
|
60
|
-
right: 0;
|
|
61
|
-
bottom: 0;
|
|
62
|
-
left: 0;
|
|
63
|
-
display: flex;
|
|
64
|
-
overflow: hidden;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
57
|
.input,
|
|
68
58
|
.character,
|
|
69
59
|
.placeholder {
|
|
@@ -82,22 +72,6 @@
|
|
|
82
72
|
color: transparent;
|
|
83
73
|
border: none;
|
|
84
74
|
font-size: 16px; // prevent iOS from automatically zooming in on focus
|
|
85
|
-
|
|
86
|
-
// Hack for this Lighthouse warning:
|
|
87
|
-
// > Interactive elements like buttons and links should be large enough (48x48px), and have
|
|
88
|
-
// > enough space around them, to be easy enough to tap without overlapping onto other elements.
|
|
89
|
-
//
|
|
90
|
-
// .inputContainer exists only for the purpose of this hack
|
|
91
|
-
$min-tile-width: 20px;
|
|
92
|
-
$min-lighthouse-size: 48px;
|
|
93
|
-
|
|
94
|
-
position: absolute;
|
|
95
|
-
top: calc((100% - $min-lighthouse-size) / 2);
|
|
96
|
-
right: calc((100% - $min-lighthouse-size) / 2);
|
|
97
|
-
bottom: calc((100% - $min-lighthouse-size) / 2);
|
|
98
|
-
left: calc((100% - $min-lighthouse-size) / 2);
|
|
99
|
-
width: $min-lighthouse-size;
|
|
100
|
-
height: $min-lighthouse-size;
|
|
101
75
|
}
|
|
102
76
|
|
|
103
77
|
.character,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
useRef,
|
|
12
12
|
} from 'react';
|
|
13
13
|
|
|
14
|
+
import { useMediaQuery } from 'hooks';
|
|
14
15
|
import { getTileSizes, noop } from 'lib';
|
|
15
16
|
import { EASE_OUT_CUBIC, TILE_APPEAR_DURATION, TILE_APPEAR_KEYFRAMES } from 'parameters';
|
|
16
17
|
import { selectLocale, useTypedSelector } from 'state';
|
|
@@ -64,7 +65,8 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
64
65
|
const ref = useRef<HTMLInputElement>(null);
|
|
65
66
|
const mergedRef = inputRef ? mergeRefs(ref, inputRef) : ref;
|
|
66
67
|
const isEmpty = !character || character === EMPTY_CELL;
|
|
67
|
-
const
|
|
68
|
+
const isLessThanXs = useMediaQuery('<xs');
|
|
69
|
+
const canShowPoints = (isBlank || !isEmpty) && typeof points !== 'undefined' && !isLessThanXs;
|
|
68
70
|
const pointsFormatted = typeof points === 'number' ? points.toLocaleString(locale) : '';
|
|
69
71
|
|
|
70
72
|
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
@@ -79,7 +81,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
79
81
|
}, [autoFocus, ref]);
|
|
80
82
|
|
|
81
83
|
useEffect(() => {
|
|
82
|
-
if (!ref.current?.parentElement || !character) {
|
|
84
|
+
if (!ref.current?.parentElement || !character || isLessThanXs) {
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -88,7 +90,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
88
90
|
easing: EASE_OUT_CUBIC,
|
|
89
91
|
fill: 'forwards',
|
|
90
92
|
});
|
|
91
|
-
}, [character]);
|
|
93
|
+
}, [character, isLessThanXs]);
|
|
92
94
|
|
|
93
95
|
return (
|
|
94
96
|
<TilePure
|
|
@@ -75,24 +75,22 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
75
75
|
})}
|
|
76
76
|
style={style}
|
|
77
77
|
>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
78
|
+
<input
|
|
79
|
+
aria-label={ariaLabel}
|
|
80
|
+
autoCapitalize="none"
|
|
81
|
+
autoComplete="off"
|
|
82
|
+
autoCorrect="off"
|
|
83
|
+
autoFocus={autoFocus}
|
|
84
|
+
className={styles.input}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
ref={inputRef}
|
|
87
|
+
spellCheck={false}
|
|
88
|
+
tabIndex={tabIndex}
|
|
89
|
+
value={character || ''}
|
|
90
|
+
onChange={onChange}
|
|
91
|
+
onFocus={onFocus}
|
|
92
|
+
onKeyDown={onKeyDown}
|
|
93
|
+
/>
|
|
96
94
|
|
|
97
95
|
{placeholder && (
|
|
98
96
|
<div className={styles.placeholder} style={characterStyle} tabIndex={-1}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import { boardSlice, localStorage, selectBoard, useTypedSelector } from 'state';
|
|
|
7
7
|
const useLocalStorageBoard = (): void => {
|
|
8
8
|
const dispatch = useDispatch();
|
|
9
9
|
const board = useTypedSelector(selectBoard);
|
|
10
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
10
11
|
|
|
11
12
|
useEffectOnce(() => {
|
|
12
13
|
const persistedBoard = localStorage.getBoard();
|
|
@@ -14,13 +15,15 @@ const useLocalStorageBoard = (): void => {
|
|
|
14
15
|
if (persistedBoard) {
|
|
15
16
|
dispatch(boardSlice.actions.init(persistedBoard));
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
setIsLoaded(true);
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
useEffect(() => {
|
|
20
|
-
if (board) {
|
|
23
|
+
if (board && isLoaded) {
|
|
21
24
|
localStorage.setBoard(board);
|
|
22
25
|
}
|
|
23
|
-
}, [board]);
|
|
26
|
+
}, [board, isLoaded]);
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export default useLocalStorageBoard;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import { localStorage, selectConfigId, settingsSlice, useTypedSelector } from 's
|
|
|
7
7
|
const useLocalStorageConfigId = (): void => {
|
|
8
8
|
const dispatch = useDispatch();
|
|
9
9
|
const configId = useTypedSelector(selectConfigId);
|
|
10
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
10
11
|
|
|
11
12
|
useEffectOnce(() => {
|
|
12
13
|
const persistedConfigId = localStorage.getConfigId();
|
|
@@ -14,13 +15,15 @@ const useLocalStorageConfigId = (): void => {
|
|
|
14
15
|
if (persistedConfigId) {
|
|
15
16
|
dispatch(settingsSlice.actions.init({ configId: persistedConfigId }));
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
setIsLoaded(true);
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
useEffect(() => {
|
|
20
|
-
if (configId) {
|
|
23
|
+
if (configId && isLoaded) {
|
|
21
24
|
localStorage.setConfigId(configId);
|
|
22
25
|
}
|
|
23
|
-
}, [configId]);
|
|
26
|
+
}, [configId, isLoaded]);
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export default useLocalStorageConfigId;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -8,6 +8,7 @@ import { localStorage, selectLocale, settingsSlice, useTypedSelector } from 'sta
|
|
|
8
8
|
const useLocalStorageLocale = (): void => {
|
|
9
9
|
const dispatch = useDispatch();
|
|
10
10
|
const locale = useTypedSelector(selectLocale);
|
|
11
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
11
12
|
|
|
12
13
|
useEffectOnce(() => {
|
|
13
14
|
const persistedLocale = localStorage.getLocale();
|
|
@@ -17,13 +18,15 @@ const useLocalStorageLocale = (): void => {
|
|
|
17
18
|
} else {
|
|
18
19
|
dispatch(settingsSlice.actions.init({ locale: detectLocale() }));
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
setIsLoaded(true);
|
|
20
23
|
});
|
|
21
24
|
|
|
22
25
|
useEffect(() => {
|
|
23
|
-
if (locale) {
|
|
26
|
+
if (locale && isLoaded) {
|
|
24
27
|
localStorage.setLocale(locale);
|
|
25
28
|
}
|
|
26
|
-
}, [locale]);
|
|
29
|
+
}, [locale, isLoaded]);
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
export default useLocalStorageLocale;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useDispatch } from 'react-redux';
|
|
3
3
|
import { useEffectOnce } from 'react-use';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import { localStorage, rackSlice, selectRack, useTypedSelector } from 'state';
|
|
|
7
7
|
const useLocalStorageRack = (): void => {
|
|
8
8
|
const dispatch = useDispatch();
|
|
9
9
|
const rack = useTypedSelector(selectRack);
|
|
10
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
10
11
|
|
|
11
12
|
useEffectOnce(() => {
|
|
12
13
|
const persistedRack = localStorage.getRack();
|
|
@@ -14,13 +15,15 @@ const useLocalStorageRack = (): void => {
|
|
|
14
15
|
if (persistedRack) {
|
|
15
16
|
dispatch(rackSlice.actions.init(persistedRack));
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
setIsLoaded(true);
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
useEffect(() => {
|
|
20
|
-
if (rack) {
|
|
23
|
+
if (rack && isLoaded) {
|
|
21
24
|
localStorage.setRack(rack);
|
|
22
25
|
}
|
|
23
|
-
}, [rack]);
|
|
26
|
+
}, [isLoaded, rack]);
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export default useLocalStorageRack;
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
display: flex;
|
|
11
11
|
align-items: center;
|
|
12
12
|
gap: var(--spacing--l);
|
|
13
|
-
font-family: var(--font--family)
|
|
13
|
+
font-family: var(--font--family--latin);
|
|
14
14
|
|
|
15
15
|
&.fa {
|
|
16
|
-
font-family: var(--font--family--arabic)
|
|
16
|
+
font-family: var(--font--family--arabic);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
package/src/parameters/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const BREAKPOINTS = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export const EASE_OUT_CUBIC = 'cubic-bezier(0.33, 1, 0.68, 1)'; // https://easings.net/#easeOutCubic
|
|
10
|
+
export const TRANSITION = 'var(--transition)';
|
|
10
11
|
|
|
11
12
|
export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
|
|
12
13
|
|
|
@@ -23,6 +24,7 @@ export const COLOR_YELLOW = '#efe3ae';
|
|
|
23
24
|
export const COMPONENTS_SPACING = 40;
|
|
24
25
|
export const COMPONENTS_SPACING_SMALL = 20;
|
|
25
26
|
|
|
27
|
+
export const BOARD_CELL_ACTIONS_OFFSET = 3;
|
|
26
28
|
export const BOARD_CELL_BORDER_WIDTH = 1;
|
|
27
29
|
export const BOARD_TILE_FONT_SIZE_MIN = 14;
|
|
28
30
|
export const BOARD_TILE_FONT_SIZE_POINTS_MIN = 10;
|
|
@@ -78,7 +80,7 @@ export const RESULTS_INPUT_HEIGHT = 40;
|
|
|
78
80
|
export const TILE_APPEAR_DURATION = 200;
|
|
79
81
|
|
|
80
82
|
export const TILE_APPEAR_KEYFRAMES = [
|
|
81
|
-
{ transform: 'translateY(0)'
|
|
82
|
-
{ transform: 'translateY(10%)', offset: 0.5
|
|
83
|
-
{ transform: 'translateY(0)'
|
|
83
|
+
{ transform: 'translateY(0)' },
|
|
84
|
+
{ transform: 'translateY(10%)', offset: 0.5 },
|
|
85
|
+
{ transform: 'translateY(0)' },
|
|
84
86
|
];
|
package/src/styles/global.scss
CHANGED
package/src/styles/mixins.scss
CHANGED
|
@@ -27,7 +27,7 @@ $media-expressions: (
|
|
|
27
27
|
right: 0;
|
|
28
28
|
bottom: 0;
|
|
29
29
|
left: 0;
|
|
30
|
-
z-index:
|
|
30
|
+
z-index: var(--z-index--focus-effect);
|
|
31
31
|
transition: var(--transition);
|
|
32
32
|
border-radius: var(--border--radius);
|
|
33
33
|
box-shadow: 0 0 0 var(--focus-effect--size) transparent;
|
|
@@ -88,3 +88,58 @@ $media-expressions: (
|
|
|
88
88
|
text-shadow: $size 0 $color, (-$size) 0 $color, 0 $size $color, 0 (-$size) $color, (-$size) (-$size) $color,
|
|
89
89
|
(-$size) $size $color, $size (-$size) $color, $size $size $color;
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
@mixin lighthouse-input-size-hack {
|
|
93
|
+
// Hack for this Lighthouse warning:
|
|
94
|
+
// > Interactive elements like buttons and links should be large enough (48x48px), and have
|
|
95
|
+
// > enough space around them, to be easy enough to tap without overlapping onto other elements.
|
|
96
|
+
|
|
97
|
+
input {
|
|
98
|
+
position: absolute;
|
|
99
|
+
top: -100%;
|
|
100
|
+
left: -100%;
|
|
101
|
+
width: 300%;
|
|
102
|
+
height: 300%;
|
|
103
|
+
clip-path: inset((100% / 3));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[dir='ltr'] & {
|
|
107
|
+
&:nth-child(1),
|
|
108
|
+
&:nth-child(2),
|
|
109
|
+
&:nth-child(3) {
|
|
110
|
+
input {
|
|
111
|
+
left: 0;
|
|
112
|
+
clip-path: polygon(0 (100% / 3), (100% / 3) (100% / 3), (100% / 3) (200% / 3), 0 (200% / 3));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&:nth-last-child(1),
|
|
117
|
+
&:nth-last-child(2),
|
|
118
|
+
&:nth-last-child(3) {
|
|
119
|
+
input {
|
|
120
|
+
left: -200%;
|
|
121
|
+
clip-path: polygon((200% / 3) (100% / 3), 100% (100% / 3), 100% (200% / 3), (200% / 3) (200% / 3));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
[dir='rtl'] & {
|
|
127
|
+
&:nth-child(1),
|
|
128
|
+
&:nth-child(2),
|
|
129
|
+
&:nth-child(3) {
|
|
130
|
+
input {
|
|
131
|
+
left: -200%;
|
|
132
|
+
clip-path: polygon((200% / 3) (100% / 3), 100% (100% / 3), 100% (200% / 3), (200% / 3) (200% / 3));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
&:nth-last-child(1),
|
|
137
|
+
&:nth-last-child(2),
|
|
138
|
+
&:nth-last-child(3) {
|
|
139
|
+
input {
|
|
140
|
+
left: 0;
|
|
141
|
+
clip-path: polygon(0 (100% / 3), (100% / 3) (100% / 3), (100% / 3) (200% / 3), 0 (200% / 3));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|