@scrabble-solver/scrabble-solver 2.16.1 → 2.16.3

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 (81) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +6 -6
  3. package/.next/cache/.previewinfo +1 -1
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  11. package/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  14. package/.next/prerender-manifest.json +4 -4
  15. package/.next/routes-manifest.json +1 -1
  16. package/.next/server/chunks/712.js +1 -1
  17. package/.next/server/middleware-build-manifest.js +1 -1
  18. package/.next/server/pages/404.html +1 -1
  19. package/.next/server/pages/500.html +1 -1
  20. package/.next/server/pages/index.html +1 -1
  21. package/.next/server/pages/index.js +1 -1
  22. package/.next/server/pages/index.json +1 -1
  23. package/.next/server/pages/not-found.html +1 -1
  24. package/.next/static/{-2mw8IrOEpCcsQa6oPYhR → 2uHbiXWVmb-guNjYk2sfF}/_buildManifest.js +1 -1
  25. package/.next/static/chunks/pages/_app-b74c31c912e414e4.js +7 -0
  26. package/.next/static/chunks/pages/index-35f70f1627836521.js +1 -0
  27. package/.next/static/css/0af35adb4bab56f0.css +2 -0
  28. package/.next/static/css/a78e04fe95452380.css +1 -0
  29. package/.next/trace +21 -21
  30. package/.next/trace-build +1 -1
  31. package/package.json +9 -9
  32. package/src/components/Board/Board.tsx +3 -1
  33. package/src/components/Board/BoardPure.tsx +3 -0
  34. package/src/components/Board/components/Cell/Cell.module.scss +6 -0
  35. package/src/components/Board/components/Cell/Cell.tsx +18 -6
  36. package/src/components/Board/lib.ts +83 -2
  37. package/src/components/Board/selectors.ts +20 -1
  38. package/src/components/DictionaryInput/DictionaryInput.tsx +1 -0
  39. package/src/components/Modal/Modal.tsx +2 -0
  40. package/src/components/Progress/Progress.tsx +5 -0
  41. package/src/components/Rack/components/RackTile/RackTile.tsx +4 -1
  42. package/src/components/Radio/Radio.tsx +1 -1
  43. package/src/components/Results/Result.tsx +1 -0
  44. package/src/components/Results/Results.tsx +8 -2
  45. package/src/components/Tile/TilePure.tsx +20 -18
  46. package/src/i18n/languages/english.json +4 -1
  47. package/src/i18n/languages/french.json +4 -1
  48. package/src/i18n/languages/german.json +4 -1
  49. package/src/i18n/languages/persian.json +4 -1
  50. package/src/i18n/languages/polish.json +4 -1
  51. package/src/i18n/languages/romanian.json +4 -1
  52. package/src/i18n/languages/spanish.json +4 -1
  53. package/src/i18n/languages/turkish.json +4 -1
  54. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +4 -0
  55. package/src/modals/RemainingTilesModal/components/Character/Character.tsx +35 -2
  56. package/src/modals/SettingsModal/SettingsModal.tsx +8 -0
  57. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +4 -4
  58. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss +12 -0
  59. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +51 -0
  60. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/index.ts +1 -0
  61. package/src/modals/SettingsModal/components/index.ts +1 -0
  62. package/src/state/hoveredTile/index.ts +4 -0
  63. package/src/state/hoveredTile/initialState.ts +5 -0
  64. package/src/state/hoveredTile/selectors.ts +3 -0
  65. package/src/state/hoveredTile/slice.ts +16 -0
  66. package/src/state/hoveredTile/types.ts +3 -0
  67. package/src/state/index.ts +1 -0
  68. package/src/state/localStorage.ts +1 -0
  69. package/src/state/settings/initialState.ts +1 -0
  70. package/src/state/settings/selectors.ts +5 -0
  71. package/src/state/settings/slice.ts +5 -0
  72. package/src/state/settings/types.ts +1 -0
  73. package/src/state/store.ts +2 -0
  74. package/src/state/types.ts +2 -0
  75. package/src/types/index.ts +3 -0
  76. package/tsconfig.tsbuildinfo +1 -1
  77. package/.next/static/chunks/pages/_app-9c9aaea7b6881765.js +0 -7
  78. package/.next/static/chunks/pages/index-e9ea63e97865edcf.js +0 -1
  79. package/.next/static/css/78726ae7cf7a5497.css +0 -2
  80. package/.next/static/css/addf6fd4f75742d0.css +0 -1
  81. /package/.next/static/{-2mw8IrOEpCcsQa6oPYhR → 2uHbiXWVmb-guNjYk2sfF}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-typescript","duration":1878442,"timestamp":17948366385,"id":13,"parentId":1,"tags":{},"startTime":1777580983909,"traceId":"f87437439890f394"},{"name":"run-webpack","duration":6032668,"timestamp":17950246945,"id":15,"parentId":1,"tags":{},"startTime":1777580985789,"traceId":"f87437439890f394"},{"name":"static-check","duration":177818,"timestamp":17956341819,"id":2026,"parentId":1,"tags":{},"startTime":1777580991884,"traceId":"f87437439890f394"},{"name":"static-generation","duration":815548,"timestamp":17956528104,"id":2044,"parentId":1,"tags":{},"startTime":1777580992070,"traceId":"f87437439890f394"},{"name":"collect-build-traces","duration":3598064,"timestamp":17956520116,"id":2041,"parentId":1,"tags":{},"startTime":1777580992062,"traceId":"f87437439890f394"},{"name":"telemetry-flush","duration":18,"timestamp":17960119320,"id":2056,"parentId":1,"tags":{},"startTime":1777580995661,"traceId":"f87437439890f394"},{"name":"next-build","duration":11983020,"timestamp":17948136322,"id":1,"tags":{"buildMode":"default","version":"16.2.4","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1777580983678,"traceId":"f87437439890f394"}]
1
+ [{"name":"run-typescript","duration":1927882,"timestamp":12132566371,"id":13,"parentId":1,"tags":{},"startTime":1777633631216,"traceId":"8b06f5414966a48b"},{"name":"run-webpack","duration":5926972,"timestamp":12134496823,"id":15,"parentId":1,"tags":{},"startTime":1777633633146,"traceId":"8b06f5414966a48b"},{"name":"static-check","duration":189599,"timestamp":12140501745,"id":2050,"parentId":1,"tags":{},"startTime":1777633639151,"traceId":"8b06f5414966a48b"},{"name":"static-generation","duration":885430,"timestamp":12140704009,"id":2068,"parentId":1,"tags":{},"startTime":1777633639353,"traceId":"8b06f5414966a48b"},{"name":"collect-build-traces","duration":3804790,"timestamp":12140691783,"id":2065,"parentId":1,"tags":{},"startTime":1777633639341,"traceId":"8b06f5414966a48b"},{"name":"telemetry-flush","duration":19,"timestamp":12144497748,"id":2080,"parentId":1,"tags":{},"startTime":1777633643147,"traceId":"8b06f5414966a48b"},{"name":"next-build","duration":12163730,"timestamp":12132334040,"id":1,"tags":{"buildMode":"default","version":"16.2.4","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1777633630983,"traceId":"8b06f5414966a48b"}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.16.1",
3
+ "version": "2.16.3",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,13 +29,13 @@
29
29
  "@floating-ui/react": "^0.27.19",
30
30
  "@kamilmielnik/trie": "^5.0.0",
31
31
  "@reduxjs/toolkit": "^2.11.2",
32
- "@scrabble-solver/configs": "^2.16.1",
33
- "@scrabble-solver/constants": "^2.16.1",
34
- "@scrabble-solver/dictionaries": "^2.16.1",
35
- "@scrabble-solver/logger": "^2.16.1",
36
- "@scrabble-solver/solver": "^2.16.1",
37
- "@scrabble-solver/types": "^2.16.1",
38
- "@scrabble-solver/word-definitions": "^2.16.1",
32
+ "@scrabble-solver/configs": "^2.16.3",
33
+ "@scrabble-solver/constants": "^2.16.3",
34
+ "@scrabble-solver/dictionaries": "^2.16.3",
35
+ "@scrabble-solver/logger": "^2.16.3",
36
+ "@scrabble-solver/solver": "^2.16.3",
37
+ "@scrabble-solver/types": "^2.16.3",
38
+ "@scrabble-solver/word-definitions": "^2.16.3",
39
39
  "classnames": "^2.5.1",
40
40
  "env-cmd": "^11.0.0",
41
41
  "include-media": "^2.0.0",
@@ -70,5 +70,5 @@
70
70
  "@types/redux": "^3.6.31",
71
71
  "sass": "^1.99.0"
72
72
  },
73
- "gitHead": "df1462399fc945fcfbf5d4d758b9922747ee3423"
73
+ "gitHead": "dd3e85ab8471dc7990bcc744f890dde9c0e34ae8"
74
74
  }
@@ -25,7 +25,7 @@ import styles from './Board.module.scss';
25
25
  import { BoardPure } from './BoardPure';
26
26
  import { Actions, InputPrompt } from './components';
27
27
  import { useBoardStyle, useFloatingActions, useFloatingFocus, useFloatingInputPrompt, useGrid } from './hooks';
28
- import { selectRowsWithCandidate } from './selectors';
28
+ import { selectReachableCells, selectRowsWithCandidate } from './selectors';
29
29
 
30
30
  interface Props {
31
31
  className?: string;
@@ -35,6 +35,7 @@ export const Board: FunctionComponent<Props> = ({ className }) => {
35
35
  const dispatch = useDispatch();
36
36
  const locale = useTypedSelector(selectLocale);
37
37
  const rows = useTypedSelector(selectRowsWithCandidate);
38
+ const reachableCells = useTypedSelector(selectReachableCells);
38
39
  const inputMode = useTypedSelector(selectInputMode);
39
40
  const cellFilters = useTypedSelector(selectCellFilters);
40
41
  const showCoordinates = useTypedSelector(selectShowCoordinates);
@@ -157,6 +158,7 @@ export const Board: FunctionComponent<Props> = ({ className }) => {
157
158
  coordinatesSize={coordinatesSize}
158
159
  direction={LOCALE_FEATURES[locale].direction}
159
160
  inputRefs={inputRefs}
161
+ reachableCells={reachableCells}
160
162
  ref={ref}
161
163
  rows={rows}
162
164
  showCoordinates={showCoordinates}
@@ -28,6 +28,7 @@ interface Props {
28
28
  coordinatesSize: number;
29
29
  direction: 'ltr' | 'rtl';
30
30
  inputRefs: RefObject<HTMLInputElement | null>[][];
31
+ reachableCells: boolean[][] | null;
31
32
  rows: CellModel[][];
32
33
  showCoordinates: ShowCoordinates;
33
34
  style?: CSSProperties;
@@ -48,6 +49,7 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
48
49
  direction,
49
50
  cellFilters,
50
51
  inputRefs,
52
+ reachableCells,
51
53
  rows,
52
54
  showCoordinates,
53
55
  style,
@@ -137,6 +139,7 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
137
139
  cellRight={x < rows[y].length - 1 ? rows[y][x + 1] : undefined}
138
140
  cellTop={y > 0 ? rows[y - 1][x] : undefined}
139
141
  inputRef={inputRefs[y][x]}
142
+ isReachable={reachableCells ? reachableCells[y][x] : true}
140
143
  key={x}
141
144
  size={cellSize}
142
145
  onChange={onChange}
@@ -87,6 +87,12 @@
87
87
  border-end-end-radius: 0;
88
88
  }
89
89
 
90
+ &.unreachable {
91
+ background-color: var(--border--color);
92
+ opacity: 0.5;
93
+ border-radius: 0;
94
+ }
95
+
90
96
  &:focus-within {
91
97
  z-index: 2;
92
98
  }
@@ -1,4 +1,4 @@
1
- import { EMPTY_CELL } from '@scrabble-solver/constants';
1
+ import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
2
2
  import { type Cell as CellModel } from '@scrabble-solver/types';
3
3
  import classNames from 'classnames';
4
4
  import {
@@ -11,11 +11,13 @@ import {
11
11
  useCallback,
12
12
  } from 'react';
13
13
 
14
+ import { getCoordinate } from '@/lib';
14
15
  import {
15
16
  selectCellIsValid,
16
17
  selectConfig,
18
+ selectHoveredCharacter,
17
19
  selectInputMode,
18
- selectLocale,
20
+ selectShowCoordinates,
19
21
  selectTilePoints,
20
22
  useTranslate,
21
23
  useTypedSelector,
@@ -33,6 +35,7 @@ interface Props {
33
35
  cellTop?: CellModel;
34
36
  className?: string;
35
37
  inputRef: RefObject<HTMLInputElement | null>;
38
+ isReachable?: boolean;
36
39
  size: number;
37
40
  onChange: ChangeEventHandler<HTMLInputElement>;
38
41
  onFocus: (x: number, y: number) => void;
@@ -46,18 +49,24 @@ export const Cell: FunctionComponent<Props> = ({
46
49
  cellTop,
47
50
  className,
48
51
  inputRef,
52
+ isReachable = true,
49
53
  size,
50
54
  onChange,
51
55
  onFocus,
52
56
  }) => {
53
57
  const { tile, x, y } = cell;
54
58
  const translate = useTranslate();
55
- const locale = useTypedSelector(selectLocale);
56
59
  const config = useTypedSelector(selectConfig);
57
60
  const inputMode = useTypedSelector(selectInputMode);
61
+ const showCoordinates = useTypedSelector(selectShowCoordinates);
58
62
  const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
59
63
  const isValid = useTypedSelector((state) => selectCellIsValid(state, cell));
64
+ const hoveredCharacter = useTypedSelector(selectHoveredCharacter);
60
65
  const isEmpty = tile.character === EMPTY_CELL;
66
+ const isHoverMatch =
67
+ !isEmpty &&
68
+ hoveredCharacter !== null &&
69
+ (hoveredCharacter === BLANK ? tile.isBlank : !tile.isBlank && tile.character === hoveredCharacter);
61
70
 
62
71
  const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
63
72
  (event) => {
@@ -96,8 +105,10 @@ export const Cell: FunctionComponent<Props> = ({
96
105
  return (
97
106
  <Tile
98
107
  aria-label={translate('cell.tile.location', {
99
- x: (x + 1).toLocaleString(locale),
100
- y: (y + 1).toLocaleString(locale),
108
+ coordinates:
109
+ showCoordinates === 'alternative'
110
+ ? `${getCoordinate(x, 'number')}${getCoordinate(y, 'letter')}`
111
+ : `${getCoordinate(x, 'letter')}${getCoordinate(y, 'number')}`,
101
112
  })}
102
113
  className={classNames(styles.tile, className, {
103
114
  [styles.first3]: x < 3,
@@ -106,9 +117,10 @@ export const Cell: FunctionComponent<Props> = ({
106
117
  [styles.sharpTopRight]: cellTop?.hasTile() || cellRight?.hasTile(),
107
118
  [styles.sharpBottomLeft]: cellBottom?.hasTile() || cellLeft?.hasTile(),
108
119
  [styles.sharpBottomRight]: cellBottom?.hasTile() || cellRight?.hasTile(),
120
+ [styles.unreachable]: !isReachable,
109
121
  })}
110
122
  character={isEmpty ? undefined : tile.character}
111
- highlighted={cell.isCandidate()}
123
+ highlighted={cell.isCandidate() || isHoverMatch}
112
124
  inputRef={inputRef}
113
125
  isBlank={tile.isBlank}
114
126
  isValid={isValid}
@@ -1,8 +1,8 @@
1
1
  import { BONUS_WORD } from '@scrabble-solver/constants';
2
- import { type Bonus } from '@scrabble-solver/types';
2
+ import { type Board, type Bonus, type Config } from '@scrabble-solver/types';
3
3
 
4
4
  import { COLOR_BONUS_CHARACTER, COLOR_BONUS_CHARACTER_MULTIPLIER, COLOR_BONUS_WORD } from '@/parameters';
5
- import { type Point } from '@/types';
5
+ import { type Direction, type Point } from '@/types';
6
6
 
7
7
  export const createGrid = <T>(width: number, height: number, getInitialValue: (x: number, y: number) => T): T[][] => {
8
8
  return Array.from({ length: height }).map((_row, y) => {
@@ -35,3 +35,84 @@ export const getPositionInGrid = <T>(grid: T[][], constraint: (value: T) => bool
35
35
 
36
36
  return undefined;
37
37
  };
38
+
39
+ export const getReachableCells = (config: Config, board: Board, charactersCount: number): boolean[][] => {
40
+ const { boardHeight, boardWidth } = config;
41
+ const reachable = createGrid(boardWidth, boardHeight, () => false);
42
+
43
+ if (charactersCount === 0) {
44
+ return reachable;
45
+ }
46
+
47
+ const isBoardEmpty = board.isEmpty();
48
+ const center = board.center;
49
+
50
+ const isAnchor = (x: number, y: number, playDirection: Direction): boolean => {
51
+ if (isBoardEmpty) {
52
+ return x === center.x && y === center.y;
53
+ }
54
+
55
+ if (playDirection === 'horizontal') {
56
+ const above = y > 0 && !board.rows[y - 1][x].isEmpty;
57
+ const below = y < boardHeight - 1 && !board.rows[y + 1][x].isEmpty;
58
+ return above || below;
59
+ }
60
+
61
+ const left = x > 0 && !board.rows[y][x - 1].isEmpty;
62
+ const right = x < boardWidth - 1 && !board.rows[y][x + 1].isEmpty;
63
+ return left || right;
64
+ };
65
+
66
+ const isReachableInDirection = (x: number, y: number, direction: Direction): boolean => {
67
+ const isHorizontal = direction === 'horizontal';
68
+ const length = isHorizontal ? boardWidth : boardHeight;
69
+ const startOffset = isHorizontal ? x : y;
70
+ const cellAt = (offset: number) => (isHorizontal ? board.rows[y][offset] : board.rows[offset][x]);
71
+
72
+ if (isAnchor(x, y, direction)) {
73
+ return true;
74
+ }
75
+
76
+ for (const step of [-1, 1] as const) {
77
+ let newTiles = 1;
78
+
79
+ for (let offset = startOffset + step; offset >= 0 && offset < length; offset += step) {
80
+ const cell = cellAt(offset);
81
+
82
+ if (cell.hasTile()) {
83
+ if (newTiles <= charactersCount) {
84
+ return true;
85
+ }
86
+ break;
87
+ }
88
+
89
+ newTiles += 1;
90
+
91
+ if (newTiles > charactersCount) {
92
+ break;
93
+ }
94
+
95
+ if (isAnchor(cell.x, cell.y, direction)) {
96
+ return true;
97
+ }
98
+ }
99
+ }
100
+
101
+ return false;
102
+ };
103
+
104
+ for (let y = 0; y < boardHeight; ++y) {
105
+ for (let x = 0; x < boardWidth; ++x) {
106
+ if (board.rows[y][x].hasTile()) {
107
+ reachable[y][x] = true;
108
+ continue;
109
+ }
110
+
111
+ if (isReachableInDirection(x, y, 'horizontal') || isReachableInDirection(x, y, 'vertical')) {
112
+ reachable[y][x] = true;
113
+ }
114
+ }
115
+ }
116
+
117
+ return reachable;
118
+ };
@@ -1,8 +1,27 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
 
3
3
  import { findCell } from '@/lib';
4
- import { selectBoard, selectResultCandidateCells } from '@/state';
4
+ import {
5
+ selectBoard,
6
+ selectCharacters,
7
+ selectConfig,
8
+ selectHighlightUnreachableCells,
9
+ selectResultCandidateCells,
10
+ } from '@/state';
11
+
12
+ import { getReachableCells } from './lib';
5
13
 
6
14
  export const selectRowsWithCandidate = createSelector([selectBoard, selectResultCandidateCells], (board, cells) => {
7
15
  return board.rows.map((row, y) => row.map((cell, x) => findCell(cells, x, y) || cell));
8
16
  });
17
+
18
+ export const selectReachableCells = createSelector(
19
+ [selectConfig, selectBoard, selectCharacters, selectHighlightUnreachableCells],
20
+ (config, board, characters, highlightUnreachableCells): boolean[][] | null => {
21
+ if (!highlightUnreachableCells || characters.length === 0) {
22
+ return null;
23
+ }
24
+
25
+ return getReachableCells(config, board, characters.length);
26
+ },
27
+ );
@@ -30,6 +30,7 @@ export const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
30
30
  return (
31
31
  <form className={classNames(styles.dictionaryInput, className)} onSubmit={handleSubmit}>
32
32
  <input
33
+ aria-label={translate('dictionary.input.title')}
33
34
  className={styles.input}
34
35
  pattern={`.*[^\\s${comma}].*`}
35
36
  placeholder={translate('dictionary.input.placeholder')}
@@ -52,6 +52,7 @@ const ModalBase: FunctionComponent<Props> = ({ children, className, footer, isOp
52
52
 
53
53
  return (
54
54
  <ReactModal
55
+ aria={{ modal: true }}
55
56
  className={{
56
57
  afterOpen: styles.afterOpen,
57
58
  base: styles.modal,
@@ -61,6 +62,7 @@ const ModalBase: FunctionComponent<Props> = ({ children, className, footer, isOp
61
62
  contentLabel={title}
62
63
  isOpen={isOpen}
63
64
  overlayClassName={styles.overlay}
65
+ role="dialog"
64
66
  shouldReturnFocusAfterClose={shouldReturnFocusAfterClose}
65
67
  onRequestClose={onClose}
66
68
  >
@@ -25,7 +25,12 @@ export const Progress: FunctionComponent<Props> = ({ className, max, min = 0, st
25
25
  return (
26
26
  <div
27
27
  {...props}
28
+ aria-valuemax={max}
29
+ aria-valuemin={min}
30
+ aria-valuenow={value}
31
+ aria-valuetext={`${percent.toLocaleString(locale)}%`}
28
32
  className={classNames(styles.progress, className)}
33
+ role="progressbar"
29
34
  style={{
30
35
  ...style,
31
36
  backgroundImage: getGradient(progress, PROGRESS_COLOR_VALUE, PROGRESS_COLOR_BACKGROUND),
@@ -21,6 +21,7 @@ import {
21
21
  selectCharacterIsValid,
22
22
  selectCharacterPoints,
23
23
  selectConfig,
24
+ selectHoveredCharacter,
24
25
  selectInputMode,
25
26
  selectLocale,
26
27
  useTranslate,
@@ -63,6 +64,8 @@ export const RackTile: FunctionComponent<Props> = ({
63
64
  const inputMode = useTypedSelector(selectInputMode);
64
65
  const points = useTypedSelector((state) => selectCharacterPoints(state, character));
65
66
  const isValid = useTypedSelector((state) => selectCharacterIsValid(state, character));
67
+ const hoveredCharacter = useTypedSelector(selectHoveredCharacter);
68
+ const isHoverMatch = character !== null && hoveredCharacter !== null && hoveredCharacter === character;
66
69
 
67
70
  const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
68
71
  (event) => {
@@ -141,7 +144,7 @@ export const RackTile: FunctionComponent<Props> = ({
141
144
  autoFocus={inputMode === 'keyboard' && index === 0}
142
145
  className={classNames(styles.rackTile, className)}
143
146
  character={character === null ? undefined : character}
144
- highlighted={tile !== null}
147
+ highlighted={tile !== null || isHoverMatch}
145
148
  inputRef={inputRef}
146
149
  isBlank={character === BLANK}
147
150
  isValid={isValid}
@@ -30,7 +30,7 @@ export const Radio: FunctionComponent<Props> = ({ checked, children, className,
30
30
  onChange={onChange}
31
31
  />
32
32
 
33
- <div aria-hidden="true" className={styles.icon} role="img">
33
+ <div aria-hidden="true" className={styles.icon}>
34
34
  <div className={styles.iconContent} />
35
35
  </div>
36
36
 
@@ -50,6 +50,7 @@ export const Result = ({
50
50
 
51
51
  return (
52
52
  <button
53
+ aria-current={index === highlightedIndex ? 'true' : undefined}
53
54
  aria-hidden={isMatching ? undefined : 'true'}
54
55
  aria-label={result.word}
55
56
  className={classNames(styles.result, {
@@ -70,10 +70,16 @@ export const Results: FunctionComponent<Props> = ({ callbacks, className, highli
70
70
  }, [results, listRef, scrollToIndexRef]);
71
71
 
72
72
  return (
73
- <div className={classNames(styles.results, className)} data-testid="results">
73
+ <div
74
+ aria-busy={isLoadingDebounced}
75
+ aria-label={translate('results')}
76
+ className={classNames(styles.results, className)}
77
+ data-testid="results"
78
+ role="region"
79
+ >
74
80
  <Header />
75
81
 
76
- <div className={styles.content}>
82
+ <div aria-live="polite" className={styles.content}>
77
83
  {typeof error !== 'undefined' && (
78
84
  <EmptyState className={styles.emptyState} variant="error">
79
85
  {error.message}
@@ -82,24 +82,26 @@ const TilePureBase: FunctionComponent<Props> = ({
82
82
  >
83
83
  {character || placeholder}
84
84
 
85
- <input
86
- aria-label={ariaLabel}
87
- autoCapitalize="none"
88
- autoComplete="off"
89
- autoCorrect="off"
90
- autoFocus={autoFocus}
91
- className={styles.input}
92
- disabled={disabled}
93
- ref={inputRef}
94
- spellCheck={false}
95
- tabIndex={tabIndex}
96
- value={character || ''}
97
- onChange={onChange}
98
- onFocus={onFocus}
99
- onKeyDown={onKeyDown}
100
- onMouseDown={onMouseDown}
101
- onTouchStart={onTouchStart}
102
- />
85
+ {!disabled && (
86
+ <input
87
+ aria-label={ariaLabel}
88
+ autoCapitalize="none"
89
+ autoComplete="off"
90
+ autoCorrect="off"
91
+ autoFocus={autoFocus}
92
+ className={styles.input}
93
+ disabled={disabled}
94
+ ref={inputRef}
95
+ spellCheck={false}
96
+ tabIndex={tabIndex}
97
+ value={character || ''}
98
+ onChange={onChange}
99
+ onFocus={onFocus}
100
+ onKeyDown={onKeyDown}
101
+ onMouseDown={onMouseDown}
102
+ onTouchStart={onTouchStart}
103
+ />
104
+ )}
103
105
 
104
106
  {canShowPoints && (
105
107
  <span className={styles.points} style={pointsStyle}>
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "Target destination",
5
5
  "cell.set-blank": "Mark it a blank",
6
6
  "cell.set-not-blank": "Mark it not a blank",
7
- "cell.tile.location": "Board: tile ({{x}}, {{y}})",
7
+ "cell.tile.location": "Board: tile {{coordinates}}",
8
8
  "cell.toggle-direction": "Typing direction",
9
9
  "common.arrows": "Arrow keys",
10
10
  "common.blanks": "Blanks",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "Consonants",
14
14
  "common.loading": "Loading",
15
15
  "common.next": "Next",
16
+ "common.off": "Off",
17
+ "common.on": "On",
16
18
  "common.points": "Points",
17
19
  "common.previous": "Previous",
18
20
  "common.space": "Spacebar",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "On the right",
65
67
  "settings.autoGroupTiles.null": "Do not group",
66
68
  "settings.game": "Game",
69
+ "settings.highlightUnreachableCells": "Highlight unreachable cells",
67
70
  "settings.inputMode": "Input mode",
68
71
  "settings.inputMode.keyboard": "Keyboard",
69
72
  "settings.inputMode.touchscreen": "Touchscreen",
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "Destination cible",
5
5
  "cell.set-blank": "Marquer comme vide",
6
6
  "cell.set-not-blank": "Marquer comme non vide",
7
- "cell.tile.location": "Plateau: la case ({{x}}, {{y}})",
7
+ "cell.tile.location": "Plateau: la case {{coordinates}}",
8
8
  "cell.toggle-direction": "Direction d'écriture",
9
9
  "common.arrows": "Touches fléchées",
10
10
  "common.blanks": "Cases vides",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "Consonnes",
14
14
  "common.loading": "Chargement",
15
15
  "common.next": "Suivant",
16
+ "common.off": "Désactivé",
17
+ "common.on": "Activé",
16
18
  "common.points": "Points",
17
19
  "common.previous": "Précédent",
18
20
  "common.space": "Barre d'espace",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "Vers la gauche",
65
67
  "settings.autoGroupTiles.null": "Ne pas grouper",
66
68
  "settings.game": "Jeu",
69
+ "settings.highlightUnreachableCells": "Mettre en évidence les cases inatteignables",
67
70
  "settings.inputMode": "Mode de saisie",
68
71
  "settings.inputMode.keyboard": "Clavier",
69
72
  "settings.inputMode.touchscreen": "Écran tactile",
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "Ziel anvisieren",
5
5
  "cell.set-blank": "Als Blanko markieren",
6
6
  "cell.set-not-blank": "Nicht als Blanko markieren",
7
- "cell.tile.location": "Brett: Stein ({{x}}, {{y}})",
7
+ "cell.tile.location": "Brett: Stein {{coordinates}}",
8
8
  "cell.toggle-direction": "Schreibrichtung",
9
9
  "common.arrows": "Pfeiltasten",
10
10
  "common.blanks": "Blankos",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "Konsonanten",
14
14
  "common.loading": "Laden",
15
15
  "common.next": "Weiter",
16
+ "common.off": "Aus",
17
+ "common.on": "Ein",
16
18
  "common.points": "Punkte",
17
19
  "common.previous": "Zurück",
18
20
  "common.space": "Leertaste",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "Rechte Seite",
65
67
  "settings.autoGroupTiles.null": "Nicht gruppieren",
66
68
  "settings.game": "Spiel",
69
+ "settings.highlightUnreachableCells": "Unerreichbare Felder hervorheben",
67
70
  "settings.inputMode": "Eingabemodus",
68
71
  "settings.inputMode.keyboard": "Tastatur",
69
72
  "settings.inputMode.touchscreen": "Touchscreen",
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "مقصد مورد نظر",
5
5
  "cell.set-blank": "علامت گذاری به عنوان خالی",
6
6
  "cell.set-not-blank": "علامت گذاری به عنوان غیر خالی",
7
- "cell.tile.location": "({{x}}، {{y}}) کاشی: صفحه",
7
+ "cell.tile.location": "{{coordinates}} کاشی: صفحه",
8
8
  "cell.toggle-direction": "جهت تایپ",
9
9
  "common.arrows": "کلیدهای جهت دار",
10
10
  "common.blanks": "خالی",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "حروف صامت",
14
14
  "common.loading": "در حال بارگزاری",
15
15
  "common.next": "بعدی",
16
+ "common.off": "خاموش",
17
+ "common.on": "روشن",
16
18
  "common.points": "امتیازات",
17
19
  "common.previous": "قبلی",
18
20
  "common.space": "دکمه اسپیس",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "در سمت راست",
65
67
  "settings.autoGroupTiles.null": "کنار هم قرار نده",
66
68
  "settings.game": "بازی",
69
+ "settings.highlightUnreachableCells": "خانه‌های غیرقابل دسترس را برجسته کن",
67
70
  "settings.inputMode": "حالت ورودی",
68
71
  "settings.inputMode.keyboard": "صفحه کلید",
69
72
  "settings.inputMode.touchscreen": "صفحه لمسی",
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "Miejsce docelowe",
5
5
  "cell.set-blank": "Oznacz jako blank",
6
6
  "cell.set-not-blank": "Oznacz jako nie blank",
7
- "cell.tile.location": "Plansza: płytka ({{x}}, {{y}})",
7
+ "cell.tile.location": "Plansza: płytka {{coordinates}}",
8
8
  "cell.toggle-direction": "Kierunek wpisywania",
9
9
  "common.arrows": "Klawisze strzałek",
10
10
  "common.blanks": "Blanki",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "Spółgłoski",
14
14
  "common.loading": "Ładowanie",
15
15
  "common.next": "Następne",
16
+ "common.off": "Wyłączone",
17
+ "common.on": "Włączone",
16
18
  "common.points": "Punkty",
17
19
  "common.previous": "Poprzednie",
18
20
  "common.space": "Spacja",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "Po prawej",
65
67
  "settings.autoGroupTiles.null": "Nie grupuj",
66
68
  "settings.game": "Gra",
69
+ "settings.highlightUnreachableCells": "Wyróżnij nieosiągalne pola",
67
70
  "settings.inputMode": "Tryb wpisywania",
68
71
  "settings.inputMode.keyboard": "Klawiatura",
69
72
  "settings.inputMode.touchscreen": "Ekran dotykowy",
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "Destinație țintă",
5
5
  "cell.set-blank": "Marcare camp liber",
6
6
  "cell.set-not-blank": "Marcare camp ocupat ",
7
- "cell.tile.location": "Tabla: camp ({{x}}, {{y}})",
7
+ "cell.tile.location": "Tabla: camp {{coordinates}}",
8
8
  "cell.toggle-direction": "Directie scriere",
9
9
  "common.arrows": "Sagetile",
10
10
  "common.blanks": "Jokeri",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "Consoane",
14
14
  "common.loading": "Incarcare",
15
15
  "common.next": "Pasul urmator",
16
+ "common.off": "Dezactivat",
17
+ "common.on": "Activat",
16
18
  "common.points": "Puncte",
17
19
  "common.previous": "Pasul anterior",
18
20
  "common.space": "Bara de spațiu",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "In partea dreapta",
65
67
  "settings.autoGroupTiles.null": "Nu se grupeaza",
66
68
  "settings.game": "Joc",
69
+ "settings.highlightUnreachableCells": "Evidențiază celulele inaccesibile",
67
70
  "settings.inputMode": "Mod introducere",
68
71
  "settings.inputMode.keyboard": "Tastatura",
69
72
  "settings.inputMode.touchscreen": "Ecran",
@@ -4,7 +4,7 @@
4
4
  "cell.filter-cell.include": "Destino objetivo",
5
5
  "cell.set-blank": "Marcar como en blanco",
6
6
  "cell.set-not-blank": "Marcar como no en blanco",
7
- "cell.tile.location": "Tablero: espacio ({{x}}, {{y}})",
7
+ "cell.tile.location": "Tablero: espacio {{coordinates}}",
8
8
  "cell.toggle-direction": "Dirección de escritura",
9
9
  "common.arrows": "Teclas de flecha",
10
10
  "common.blanks": "Blancos",
@@ -13,6 +13,8 @@
13
13
  "common.consonants": "Consonantes",
14
14
  "common.loading": "Cargando",
15
15
  "common.next": "Siguiente",
16
+ "common.off": "Desactivado",
17
+ "common.on": "Activado",
16
18
  "common.points": "Puntos",
17
19
  "common.previous": "Anterior",
18
20
  "common.space": "Barra espaciadora",
@@ -64,6 +66,7 @@
64
66
  "settings.autoGroupTiles.right": "A la derecha",
65
67
  "settings.autoGroupTiles.null": "No agrupar",
66
68
  "settings.game": "Juego",
69
+ "settings.highlightUnreachableCells": "Resaltar casillas inalcanzables",
67
70
  "settings.inputMode": "Modo de entrada",
68
71
  "settings.inputMode.keyboard": "Teclado",
69
72
  "settings.inputMode.touchscreen": "Pantalla táctil",