scrabble-solver 2.10.7 → 2.10.9

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 (148) hide show
  1. package/babel.config.js +1 -1
  2. package/lerna.json +1 -1
  3. package/package.json +3 -3
  4. package/packages/configs/package-lock.json +4 -4
  5. package/packages/configs/package.json +3 -3
  6. package/packages/constants/package-lock.json +2 -2
  7. package/packages/constants/package.json +1 -1
  8. package/packages/dictionaries/package-lock.json +2 -2
  9. package/packages/dictionaries/package.json +4 -4
  10. package/packages/logger/package-lock.json +2 -2
  11. package/packages/logger/package.json +1 -1
  12. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  13. package/packages/scrabble-solver/.next/build-manifest.json +15 -21
  14. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  15. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  16. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  17. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  18. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  19. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
  20. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  21. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  22. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  23. package/packages/scrabble-solver/.next/images-manifest.json +1 -1
  24. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  25. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  26. package/packages/scrabble-solver/.next/required-server-files.json +1 -1
  27. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  28. package/packages/scrabble-solver/.next/server/chunks/176.js +4603 -395
  29. package/packages/scrabble-solver/.next/server/chunks/664.js +27 -2414
  30. package/packages/scrabble-solver/.next/server/chunks/859.js +29 -1
  31. package/packages/scrabble-solver/.next/server/font-loader-manifest.js +1 -0
  32. package/packages/scrabble-solver/.next/server/font-loader-manifest.json +6 -0
  33. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  34. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  35. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  36. package/packages/scrabble-solver/.next/server/pages/500.html +1 -12
  37. package/packages/scrabble-solver/.next/server/pages/_app.js +176 -13
  38. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  39. package/packages/scrabble-solver/.next/server/pages/_document.js +1 -1
  40. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  41. package/packages/scrabble-solver/.next/server/pages/_error.js +133 -17
  42. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  43. package/packages/scrabble-solver/.next/server/pages/api/solve.js +16 -0
  44. package/packages/scrabble-solver/.next/server/pages/index.html +1 -9
  45. package/packages/scrabble-solver/.next/server/pages/index.js +52 -165
  46. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  47. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  48. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  49. package/packages/scrabble-solver/.next/static/chunks/main-74c4d6b2b5c362f3.js +1 -0
  50. package/packages/scrabble-solver/.next/static/chunks/pages/{404-67383848027ec49b.js → 404-d5ff00df1c687977.js} +1 -1
  51. package/packages/scrabble-solver/.next/static/chunks/pages/_app-52cb288dc680bdfe.js +28 -0
  52. package/packages/scrabble-solver/.next/static/chunks/pages/index-5c2544930e46c5ce.js +1 -0
  53. package/packages/scrabble-solver/.next/static/chunks/webpack-6ef43a8d4a395f49.js +1 -0
  54. package/packages/scrabble-solver/.next/static/css/ec4e47a6b1866fe5.css +1 -0
  55. package/packages/scrabble-solver/.next/static/css/f65b7b2a74f57c1c.css +2 -0
  56. package/packages/scrabble-solver/.next/static/fZRsz4P0gQ8Wgb9jP8eap/_buildManifest.js +1 -0
  57. package/packages/scrabble-solver/.next/trace +55 -55
  58. package/packages/scrabble-solver/package-lock.json +255 -122
  59. package/packages/scrabble-solver/package.json +11 -9
  60. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss +1 -1
  61. package/packages/scrabble-solver/src/components/Board/Board.module.scss +14 -0
  62. package/packages/scrabble-solver/src/components/Board/Board.tsx +117 -19
  63. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +7 -15
  64. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +64 -0
  65. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +68 -0
  66. package/packages/scrabble-solver/src/components/Board/components/Actions/index.ts +1 -0
  67. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +22 -165
  68. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +0 -37
  69. package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +5 -75
  70. package/packages/scrabble-solver/src/components/Board/components/index.ts +1 -0
  71. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +16 -16
  72. package/packages/scrabble-solver/src/components/Button/Button.module.scss +3 -3
  73. package/packages/scrabble-solver/src/components/Checkbox/Checkbox.tsx +1 -4
  74. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +28 -30
  75. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +3 -0
  76. package/packages/scrabble-solver/src/components/Key/Key.module.scss +1 -1
  77. package/packages/scrabble-solver/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +1 -1
  78. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss +4 -2
  79. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +5 -0
  80. package/packages/scrabble-solver/src/components/Radio/Radio.tsx +1 -4
  81. package/packages/scrabble-solver/src/components/Results/Results.module.scss +2 -2
  82. package/packages/scrabble-solver/src/components/SeoMessage/SeoMessage.tsx +19 -0
  83. package/packages/scrabble-solver/src/components/SeoMessage/index.ts +1 -0
  84. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +0 -5
  85. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +1 -1
  86. package/packages/scrabble-solver/src/components/Solver/components/EmptyState/EmptyState.module.scss +1 -1
  87. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +9 -2
  88. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -1
  89. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +23 -11
  90. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +26 -9
  91. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +9 -4
  92. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss +7 -7
  93. package/packages/scrabble-solver/src/components/index.ts +1 -0
  94. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageBoard.ts +6 -3
  95. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +6 -3
  96. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageLocale.ts +6 -3
  97. package/packages/scrabble-solver/src/hooks/useLocalStorage/useLocalStorageRack.ts +6 -3
  98. package/packages/scrabble-solver/src/i18n/de.json +1 -0
  99. package/packages/scrabble-solver/src/i18n/en.json +1 -0
  100. package/packages/scrabble-solver/src/i18n/es.json +1 -0
  101. package/packages/scrabble-solver/src/i18n/fa.json +1 -0
  102. package/packages/scrabble-solver/src/i18n/fr.json +1 -0
  103. package/packages/scrabble-solver/src/i18n/pl.json +1 -0
  104. package/packages/scrabble-solver/src/icons/Flag.svg +2 -2
  105. package/packages/scrabble-solver/src/icons/FlagFill.svg +4 -0
  106. package/packages/scrabble-solver/src/icons/Square.svg +4 -0
  107. package/packages/scrabble-solver/src/icons/SquareFill.svg +4 -0
  108. package/packages/scrabble-solver/src/icons/index.ts +3 -0
  109. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
  110. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss +1 -1
  111. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -2
  112. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +1 -1
  113. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -2
  114. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +14 -24
  115. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +1 -2
  116. package/packages/scrabble-solver/src/pages/_app.tsx +9 -5
  117. package/packages/scrabble-solver/src/pages/index.module.scss +1 -2
  118. package/packages/scrabble-solver/src/pages/index.tsx +10 -8
  119. package/packages/scrabble-solver/src/parameters/index.ts +12 -0
  120. package/packages/scrabble-solver/src/state/slices/boardSlice.ts +5 -5
  121. package/packages/scrabble-solver/src/styles/animations.scss +10 -0
  122. package/packages/scrabble-solver/src/styles/global.scss +2 -2
  123. package/packages/scrabble-solver/src/styles/mixins.scss +60 -3
  124. package/packages/scrabble-solver/src/styles/variables.scss +43 -33
  125. package/packages/scrabble-solver/src/types/index.ts +1 -0
  126. package/packages/solver/package-lock.json +2 -2
  127. package/packages/solver/package.json +5 -5
  128. package/packages/types/package-lock.json +3 -3
  129. package/packages/types/package.json +2 -2
  130. package/packages/word-definitions/package-lock.json +2 -2
  131. package/packages/word-definitions/package.json +2 -2
  132. package/packages/word-lists/package-lock.json +2 -2
  133. package/packages/word-lists/package.json +2 -2
  134. package/packages/scrabble-solver/.next/server/chunks/210.js +0 -122
  135. package/packages/scrabble-solver/.next/server/chunks/579.js +0 -3925
  136. package/packages/scrabble-solver/.next/server/chunks/676.js +0 -32
  137. package/packages/scrabble-solver/.next/static/6RggBFm8kHrh-k1-CG3um/_buildManifest.js +0 -1
  138. package/packages/scrabble-solver/.next/static/chunks/490-d29992f1c264d70e.js +0 -5
  139. package/packages/scrabble-solver/.next/static/chunks/509-6ad4482d4351452c.js +0 -1
  140. package/packages/scrabble-solver/.next/static/chunks/main-f11614d8aa7ee555.js +0 -1
  141. package/packages/scrabble-solver/.next/static/chunks/pages/_app-c58cfa832b76cc87.js +0 -24
  142. package/packages/scrabble-solver/.next/static/chunks/pages/index-146039f501e49c08.js +0 -1
  143. package/packages/scrabble-solver/.next/static/chunks/webpack-59c5c889f52620d6.js +0 -1
  144. package/packages/scrabble-solver/.next/static/css/4482c4a0064d3807.css +0 -1
  145. package/packages/scrabble-solver/.next/static/css/78e42ad01f580f64.css +0 -1
  146. package/packages/scrabble-solver/.next/static/css/9d1013ec684361b9.css +0 -1
  147. package/packages/scrabble-solver/src/components/Board/components/Cell/Button.tsx +0 -32
  148. /package/packages/scrabble-solver/.next/static/{6RggBFm8kHrh-k1-CG3um → fZRsz4P0gQ8Wgb9jP8eap}/_ssgManifest.js +0 -0
@@ -1,12 +1,9 @@
1
1
  import { EMPTY_CELL } from '@scrabble-solver/constants';
2
2
  import { Cell as CellModel } from '@scrabble-solver/types';
3
3
  import { ChangeEventHandler, FunctionComponent, RefObject, useCallback, useMemo } from 'react';
4
- import { useDispatch } from 'react-redux';
5
4
 
6
5
  import { getTileSizes } from 'lib';
7
6
  import {
8
- boardSlice,
9
- cellFilterSlice,
10
7
  selectCellBonus,
11
8
  selectCellIsFiltered,
12
9
  selectCellIsValid,
@@ -21,32 +18,27 @@ import CellPure from './CellPure';
21
18
  interface Props {
22
19
  cell: CellModel;
23
20
  className?: string;
24
- direction: 'horizontal' | 'vertical';
25
21
  inputRef: RefObject<HTMLInputElement>;
26
22
  isBottom: boolean;
27
23
  isCenter: boolean;
28
24
  isRight: boolean;
29
25
  size: number;
30
26
  onChange: ChangeEventHandler<HTMLInputElement>;
31
- onDirectionToggle: () => void;
32
27
  onFocus: (x: number, y: number) => void;
33
28
  }
34
29
 
35
30
  const Cell: FunctionComponent<Props> = ({
36
31
  cell,
37
32
  className,
38
- direction,
39
33
  inputRef,
40
34
  isBottom,
41
35
  isCenter,
42
36
  isRight,
43
37
  size,
44
38
  onChange,
45
- onDirectionToggle,
46
39
  onFocus,
47
40
  }) => {
48
41
  const { tile, x, y } = cell;
49
- const dispatch = useDispatch();
50
42
  const translate = useTranslate();
51
43
  const locale = useTypedSelector(selectLocale);
52
44
  const bonus = useTypedSelector((state) => selectCellBonus(state, cell));
@@ -57,32 +49,8 @@ const Cell: FunctionComponent<Props> = ({
57
49
  const isEmpty = tile.character === EMPTY_CELL;
58
50
  const style = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
59
51
 
60
- const handleDirectionToggleClick = useCallback(() => {
61
- if (inputRef.current) {
62
- inputRef.current.focus();
63
- }
64
-
65
- onDirectionToggle();
66
- }, [onDirectionToggle]);
67
-
68
52
  const handleFocus = useCallback(() => onFocus(x, y), [x, y, onFocus]);
69
53
 
70
- const handleToggleBlankClick = useCallback(() => {
71
- if (inputRef.current) {
72
- inputRef.current.focus();
73
- }
74
-
75
- dispatch(boardSlice.actions.toggleCellIsBlank({ x, y }));
76
- }, [dispatch, x, y]);
77
-
78
- const handleToggleFilterCellClick = useCallback(() => {
79
- if (inputRef.current) {
80
- inputRef.current.focus();
81
- }
82
-
83
- dispatch(cellFilterSlice.actions.toggle({ x, y }));
84
- }, [dispatch, x, y]);
85
-
86
54
  return (
87
55
  <CellPure
88
56
  aria-label={translate('cell.tile.location', {
@@ -92,7 +60,6 @@ const Cell: FunctionComponent<Props> = ({
92
60
  bonus={bonus}
93
61
  cell={cell}
94
62
  className={className}
95
- direction={direction}
96
63
  inputRef={inputRef}
97
64
  isBottom={isBottom}
98
65
  isCenter={isCenter}
@@ -104,12 +71,8 @@ const Cell: FunctionComponent<Props> = ({
104
71
  size={size}
105
72
  style={style}
106
73
  tile={tile}
107
- translate={translate}
108
74
  onChange={onChange}
109
- onDirectionToggleClick={handleDirectionToggleClick}
110
75
  onFocus={handleFocus}
111
- onToggleBlankClick={handleToggleBlankClick}
112
- onToggleFilterCellClick={handleToggleFilterCellClick}
113
76
  />
114
77
  );
115
78
  };
@@ -1,21 +1,11 @@
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 { ArrowDown, Flag, Star } from 'icons';
14
- import { Translate } from 'types';
5
+ import { FlagFill, Star } from 'icons';
15
6
 
16
7
  import Tile from '../../../Tile';
17
8
 
18
- import Button from './Button';
19
9
  import styles from './Cell.module.scss';
20
10
  import { getBonusClassname } from './lib';
21
11
 
@@ -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.candidate]: cell.isCandidate(),
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
- <Flag className={styles.flag} />
88
- </div>
89
- )}
61
+ {isFiltered && <FlagFill className={styles.icon} />}
90
62
 
91
63
  <Tile
92
64
  aria-label={ariaLabel}
@@ -103,48 +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
- tooltip={translate('cell.toggle-direction')}
112
- onClick={onDirectionToggleClick}
113
- >
114
- <ArrowDown
115
- className={classNames(styles.toggleDirection, {
116
- [styles.right]: direction === 'horizontal',
117
- })}
118
- />
119
- </Button>
120
-
121
- {isEmpty && (
122
- <Button
123
- aria-label={translate('cell.filter-cell')}
124
- className={classNames(styles.filterCell, {
125
- [styles.filtered]: isFiltered,
126
- })}
127
- tooltip={translate('cell.filter-cell')}
128
- onClick={onToggleFilterCellClick}
129
- >
130
- <Flag />
131
- </Button>
132
- )}
133
-
134
- {!isEmpty && (
135
- <Button
136
- aria-label={tile.isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
137
- className={classNames(styles.blank, {
138
- [styles.active]: tile.isBlank,
139
- })}
140
- tooltip={tile.isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
141
- onClick={onToggleBlankClick}
142
- >
143
- B
144
- </Button>
145
- )}
146
- </div>
147
- )}
148
78
  </div>
149
79
  );
150
80
 
@@ -1 +1,2 @@
1
+ export { default as Actions } from './Actions';
1
2
  export { default as Cell } from './Cell';
@@ -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
- refs: RefObject<HTMLInputElement>[][];
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 refs = useMemo(
49
+ const inputRefs = useMemo(
50
50
  () => createGridOf<RefObject<HTMLInputElement>>(width, height, () => createRef()),
51
51
  [width, height],
52
52
  );
53
- const activeIndexRef = useRef<Point>({ x: 0, y: 0 });
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(activeIndexRef.current.x + offsetX, 0), width - 1);
60
- const y = Math.min(Math.max(activeIndexRef.current.y + offsetY, 0), height - 1);
61
- activeIndexRef.current = { x, y };
62
- refs[y][x].current?.focus();
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
- [activeIndexRef, refs],
64
+ [activeIndex, inputRefs],
65
65
  );
66
66
 
67
67
  const getInputRefPosition = useCallback(
68
68
  (inputRef: HTMLInputElement): Point | undefined => {
69
- return getPositionInGrid(refs, (ref) => ref.current === inputRef);
69
+ return getPositionInGrid(inputRefs, (ref) => ref.current === inputRef);
70
70
  },
71
- [refs],
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
- activeIndexRef.current = { x, y };
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, refs },
359
+ { activeIndex, direction, inputRefs },
360
360
  { onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
361
361
  ];
362
362
  };
@@ -21,15 +21,15 @@
21
21
 
22
22
  &.primary {
23
23
  background-color: var(--color--primary);
24
- color: white;
24
+ color: var(--color--primary--opposite);
25
25
  }
26
26
 
27
27
  &.default {
28
- background-color: var(--color--background--overlay);
28
+ background-color: var(--color--background--element);
29
29
  color: var(--color--foreground);
30
30
 
31
31
  .icon {
32
- color: var(--color--inactive);
32
+ color: var(--color--foreground--secondary);
33
33
  }
34
34
  }
35
35
  }
@@ -10,23 +10,20 @@ interface Props {
10
10
  children?: ReactNode;
11
11
  className?: string;
12
12
  disabled?: boolean;
13
- id: string;
14
13
  name: string;
15
14
  onChange: ChangeEventHandler<HTMLInputElement>;
16
15
  }
17
16
 
18
- const Checkbox: FunctionComponent<Props> = ({ checked, children, className, disabled, id, name, onChange }) => (
17
+ const Checkbox: FunctionComponent<Props> = ({ checked, children, className, disabled, name, onChange }) => (
19
18
  <label
20
19
  className={classNames(styles.checkbox, className, {
21
20
  [styles.checked]: checked,
22
21
  })}
23
- htmlFor={id}
24
22
  >
25
23
  <input
26
24
  checked={checked}
27
25
  className={styles.input}
28
26
  disabled={disabled}
29
- id={id}
30
27
  name={name}
31
28
  type="checkbox"
32
29
  onChange={onChange}
@@ -27,6 +27,10 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
27
27
  >
28
28
  {typeof error !== 'undefined' && <EmptyState variant="error">{error.message}</EmptyState>}
29
29
 
30
+ {results.length === 0 && (
31
+ <EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
32
+ )}
33
+
30
34
  {results.map(({ definitions, exists, isAllowed, word }) => (
31
35
  <div
32
36
  className={classNames(styles.result, {
@@ -35,40 +39,34 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
35
39
  })}
36
40
  key={word}
37
41
  >
38
- {typeof word === 'undefined' && (
39
- <EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
40
- )}
41
-
42
- {typeof word !== 'undefined' && (
43
- <div className={styles.content}>
44
- {word && <h2 className={styles.word}>{word}</h2>}
42
+ <div className={styles.content}>
43
+ {word && <h2 className={styles.word}>{word}</h2>}
45
44
 
46
- {isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
45
+ {isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
47
46
 
48
- {isAllowed === true && (
49
- <>
50
- {definitions.length === 0 && (
51
- <>
52
- {exists && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
53
- {!exists && <div>{translate('dictionary.empty-state.no-results')}</div>}
54
- </>
55
- )}
47
+ {isAllowed === true && (
48
+ <>
49
+ {definitions.length === 0 && (
50
+ <>
51
+ {exists && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
52
+ {!exists && <div>{translate('dictionary.empty-state.no-results')}</div>}
53
+ </>
54
+ )}
56
55
 
57
- {definitions.length > 0 && (
58
- <ul className={styles.definitions}>
59
- {definitions.map((result, index) => (
60
- <li key={index} className={styles.definition}>
61
- {result}
62
- </li>
63
- ))}
64
- </ul>
65
- )}
66
- </>
67
- )}
56
+ {definitions.length > 0 && (
57
+ <ul className={styles.definitions}>
58
+ {definitions.map((result, index) => (
59
+ <li key={index} className={styles.definition}>
60
+ {result}
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ )}
65
+ </>
66
+ )}
68
67
 
69
- {!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
70
- </div>
71
- )}
68
+ {!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
69
+ </div>
72
70
  </div>
73
71
  ))}
74
72
 
@@ -1,3 +1,4 @@
1
+ import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
1
2
  import classNames from 'classnames';
2
3
  import { ChangeEvent, FormEvent, FunctionComponent } from 'react';
3
4
  import { useDispatch } from 'react-redux';
@@ -28,8 +29,10 @@ const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
28
29
  <form className={classNames(styles.dictionaryInput, className)} onSubmit={handleSubmit}>
29
30
  <input
30
31
  className={styles.input}
32
+ pattern={`.*[^\\s${COMMA_ARABIC}${COMMA_LATIN}].*`}
31
33
  placeholder={translate('dictionary.input.placeholder')}
32
34
  required
35
+ title={translate('dictionary.input.title')}
33
36
  type="text"
34
37
  value={input}
35
38
  onChange={handleChange}
@@ -8,7 +8,7 @@ $icon-size: 15px;
8
8
  min-width: $key-size;
9
9
  height: $key-size;
10
10
  padding: var(--spacing--s) var(--spacing--m);
11
- background-color: white;
11
+ background-color: var(--color--white);
12
12
  border: var(--border);
13
13
  border-radius: var(--border--radius);
14
14
  box-shadow: var(--box-shadow);
@@ -43,7 +43,7 @@ $hiding-duration: 200ms;
43
43
 
44
44
  .logoGrayscale {
45
45
  filter: grayscale(1);
46
- opacity: 0.3;
46
+ opacity: var(--opacity--disabled);
47
47
  }
48
48
 
49
49
  .logoColor {
@@ -60,12 +60,14 @@
60
60
  }
61
61
 
62
62
  .container {
63
+ --box-shadow--offset: 1px;
64
+ --box-shadow--offset--negative: calc(-1 * var(--box-shadow--offset));
65
+
63
66
  display: flex;
64
67
  flex-direction: column;
65
68
  height: 100%;
66
69
  background-color: var(--color--background);
67
- transition: var(--transition);
68
- transition-duration: var(--transition--duration--long);
70
+ transition: var(--transition--long);
69
71
  transform: translateX(var(--modal--width));
70
72
  opacity: 0;
71
73
 
@@ -7,6 +7,11 @@
7
7
 
8
8
  .tile {
9
9
  @include focus-effect;
10
+ @include lighthouse-input-size-hack;
10
11
 
11
12
  --background-color: var(--color--background);
13
+
14
+ &:focus-within {
15
+ z-index: 2;
16
+ }
12
17
  }
@@ -8,24 +8,21 @@ interface Props {
8
8
  children?: ReactNode;
9
9
  className?: string;
10
10
  disabled?: boolean;
11
- id: string;
12
11
  name: string;
13
12
  value: string;
14
13
  onChange: ChangeEventHandler<HTMLInputElement>;
15
14
  }
16
15
 
17
- const Radio: FunctionComponent<Props> = ({ checked, children, className, disabled, id, name, value, onChange }) => (
16
+ const Radio: FunctionComponent<Props> = ({ checked, children, className, disabled, name, value, onChange }) => (
18
17
  <label
19
18
  className={classNames(styles.radio, className, {
20
19
  [styles.checked]: checked,
21
20
  })}
22
- htmlFor={id}
23
21
  >
24
22
  <input
25
23
  checked={checked}
26
24
  className={styles.input}
27
25
  disabled={disabled}
28
- id={id}
29
26
  name={name}
30
27
  type="radio"
31
28
  value={value}
@@ -45,7 +45,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
45
45
  &:focus,
46
46
  &:hover {
47
47
  background-color: var(--color--primary);
48
- color: white;
48
+ color: var(--color--primary--opposite);
49
49
  }
50
50
  }
51
51
 
@@ -73,7 +73,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
73
73
  &.highlighted {
74
74
  &:not(:disabled) {
75
75
  background-color: var(--color--primary);
76
- color: white;
76
+ color: var(--color--primary--opposite);
77
77
  }
78
78
  }
79
79
  }
@@ -0,0 +1,19 @@
1
+ import { CSSProperties, FunctionComponent } from 'react';
2
+
3
+ const INVISIBLE_STYLE: CSSProperties = {
4
+ color: 'transparent',
5
+ pointerEvents: 'none',
6
+ position: 'absolute',
7
+ userSelect: 'none',
8
+ transform: 'translateY(-9999px)',
9
+ };
10
+
11
+ const SeoMessage: FunctionComponent = () => (
12
+ <p style={INVISIBLE_STYLE}>
13
+ Scrabble Solver 2 is a free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring words
14
+ using given letters and board state. Available in English, French, German, Polish & Spanish. Source code is
15
+ available on GitHub - contributions are welcome!
16
+ </p>
17
+ );
18
+
19
+ export default SeoMessage;
@@ -0,0 +1 @@
1
+ export { default } from './SeoMessage';
@@ -96,11 +96,6 @@
96
96
  min-width: 0;
97
97
  }
98
98
 
99
- .rackContainer {
100
- display: flex;
101
- justify-content: center;
102
- }
103
-
104
99
  .rack {
105
100
  border: var(--border);
106
101
  }
@@ -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 className={styles.rackContainer} onSubmit={handleSubmit}>
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>
@@ -4,7 +4,7 @@
4
4
  width: 100%;
5
5
  border: var(--border);
6
6
  border-radius: var(--border--radius);
7
- background-color: white;
7
+ background-color: var(--color--white);
8
8
  text-align: center;
9
9
  }
10
10
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  .resultCandidatePicker {
4
4
  display: flex;
5
- align-items: center;
6
5
  gap: var(--spacing--l);
7
6
 
8
7
  @include media('<xs') {
@@ -14,6 +13,14 @@
14
13
  flex: 0 0 auto;
15
14
  display: flex;
16
15
  border-radius: var(--border--radius);
16
+
17
+ &.bothEnabled {
18
+ box-shadow: var(--box-shadow);
19
+
20
+ .button {
21
+ box-shadow: none;
22
+ }
23
+ }
17
24
  }
18
25
 
19
26
  .button {
@@ -50,7 +57,7 @@
50
57
  width: 100%;
51
58
  border: var(--border);
52
59
  border-radius: var(--border--radius);
53
- background-color: white;
60
+ background-color: var(--color--white);
54
61
  box-shadow: var(--box-shadow);
55
62
  cursor: pointer;
56
63
 
@@ -34,6 +34,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
34
34
  const disabled = isOutdated || !resultCandidate;
35
35
  const isPreviousDisabled = index <= 0 || disabled;
36
36
  const isNextDisabled = index >= results.length - 1 || disabled;
37
+ const bothEnabled = !isPreviousDisabled && !isNextDisabled;
37
38
 
38
39
  const handleNextClick = () => {
39
40
  if (!isNextDisabled) {
@@ -51,7 +52,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
51
52
 
52
53
  return (
53
54
  <div className={classNames(styles.resultCandidatePicker, className)} {...props}>
54
- <div className={styles.buttons}>
55
+ <div className={classNames(styles.buttons, { [styles.bothEnabled]: bothEnabled })}>
55
56
  <Button
56
57
  aria-label={translate('common.previous')}
57
58
  className={styles.button}