@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.
Files changed (87) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +15 -15
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +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/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/images-manifest.json +1 -1
  13. package/.next/next-server.js.nft.json +1 -1
  14. package/.next/prerender-manifest.json +1 -1
  15. package/.next/required-server-files.json +1 -1
  16. package/.next/routes-manifest.json +1 -1
  17. package/.next/server/chunks/131.js +24 -17
  18. package/.next/server/chunks/176.js +443 -288
  19. package/.next/server/chunks/50.js +712 -1025
  20. package/.next/server/chunks/664.js +27 -2414
  21. package/.next/server/chunks/859.js +29 -1
  22. package/.next/server/font-loader-manifest.js +1 -0
  23. package/.next/server/font-loader-manifest.json +6 -0
  24. package/.next/server/middleware-build-manifest.js +1 -1
  25. package/.next/server/pages/404.html +2 -2
  26. package/.next/server/pages/404.js.nft.json +1 -1
  27. package/.next/server/pages/500.html +1 -12
  28. package/.next/server/pages/_app.js +13 -133
  29. package/.next/server/pages/_app.js.nft.json +1 -1
  30. package/.next/server/pages/_document.js +1 -1
  31. package/.next/server/pages/_document.js.nft.json +1 -1
  32. package/.next/server/pages/_error.js +133 -17
  33. package/.next/server/pages/_error.js.nft.json +1 -1
  34. package/.next/server/pages/api/solve.js +5 -6
  35. package/.next/server/pages/index.html +1 -1
  36. package/.next/server/pages/index.js +34 -154
  37. package/.next/server/pages/index.js.nft.json +1 -1
  38. package/.next/server/pages/index.json +1 -1
  39. package/.next/server/pages-manifest.json +1 -1
  40. package/.next/static/chunks/main-74c4d6b2b5c362f3.js +1 -0
  41. package/.next/static/chunks/pages/{404-8cab6d62fe4ead73.js → 404-d5ff00df1c687977.js} +1 -1
  42. package/.next/static/chunks/pages/_app-3272e798504c40d8.js +28 -0
  43. package/.next/static/chunks/pages/index-5c2544930e46c5ce.js +1 -0
  44. package/.next/static/chunks/webpack-6ef43a8d4a395f49.js +1 -0
  45. package/.next/static/css/336e75db2b74b157.css +2 -0
  46. package/.next/static/css/{d1cc6b79b211b7b8.css → ec4e47a6b1866fe5.css} +1 -1
  47. package/.next/static/warzWo25tDxo_Eiv9T6f2/_buildManifest.js +1 -0
  48. package/.next/trace +55 -55
  49. package/package.json +12 -11
  50. package/src/components/Board/Board.module.scss +14 -0
  51. package/src/components/Board/Board.tsx +117 -19
  52. package/src/components/Board/BoardPure.tsx +7 -15
  53. package/src/components/Board/components/Actions/Actions.module.scss +64 -0
  54. package/src/components/Board/components/Actions/Actions.tsx +74 -0
  55. package/src/components/Board/components/Actions/index.ts +1 -0
  56. package/src/components/Board/components/Cell/Cell.module.scss +10 -121
  57. package/src/components/Board/components/Cell/Cell.tsx +0 -37
  58. package/src/components/Board/components/Cell/CellPure.tsx +5 -68
  59. package/src/components/Board/components/index.ts +1 -0
  60. package/src/components/Board/hooks/useGrid.ts +16 -16
  61. package/src/components/Modal/Modal.module.scss +3 -1
  62. package/src/components/Rack/Rack.module.scss +2 -1
  63. package/src/components/Solver/Solver.module.scss +0 -5
  64. package/src/components/Solver/Solver.tsx +1 -1
  65. package/src/components/Tile/Tile.module.scss +0 -26
  66. package/src/components/Tile/Tile.tsx +5 -3
  67. package/src/components/Tile/TilePure.tsx +16 -18
  68. package/src/hooks/useLocalStorage/useLocalStorageBoard.ts +6 -3
  69. package/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +6 -3
  70. package/src/hooks/useLocalStorage/useLocalStorageLocale.ts +6 -3
  71. package/src/hooks/useLocalStorage/useLocalStorageRack.ts +6 -3
  72. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +1 -1
  73. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +2 -2
  74. package/src/parameters/index.ts +5 -3
  75. package/src/styles/animations.scss +10 -0
  76. package/src/styles/global.scss +2 -2
  77. package/src/styles/mixins.scss +56 -1
  78. package/src/styles/variables.scss +5 -2
  79. package/.next/server/chunks/210.js +0 -122
  80. package/.next/server/chunks/676.js +0 -32
  81. package/.next/static/Cs23uxWG6AxS72F2yrjHu/_buildManifest.js +0 -1
  82. package/.next/static/chunks/main-f11614d8aa7ee555.js +0 -1
  83. package/.next/static/chunks/pages/_app-dcbbb823dc93a031.js +0 -28
  84. package/.next/static/chunks/pages/index-df1ff01aa82d2d4d.js +0 -1
  85. package/.next/static/chunks/webpack-59c5c889f52620d6.js +0 -1
  86. package/.next/static/css/bf2e969b88c4e3dd.css +0 -2
  87. /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 { ArrowDown, Flag, FlagFill, Square, SquareFill, Star } from 'icons';
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.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
- <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
 
@@ -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
  };
@@ -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;
@@ -7,10 +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);
12
13
 
13
14
  &:focus-within {
14
- z-index: 1;
15
+ z-index: 2;
15
16
  }
16
17
  }
@@ -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>
@@ -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 canShowPoints = (isBlank || !isEmpty) && typeof points !== 'undefined';
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
- <div className={styles.inputContainer}>
79
- <input
80
- aria-label={ariaLabel}
81
- autoCapitalize="none"
82
- autoComplete="off"
83
- autoCorrect="off"
84
- autoFocus={autoFocus}
85
- className={styles.input}
86
- disabled={disabled}
87
- ref={inputRef}
88
- spellCheck={false}
89
- tabIndex={tabIndex}
90
- value={character || ''}
91
- onChange={onChange}
92
- onFocus={onFocus}
93
- onKeyDown={onKeyDown}
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;
@@ -9,5 +9,5 @@
9
9
  .label {
10
10
  display: flex;
11
11
  align-items: center;
12
- font-family: 'Open Sans', sans-serif;
12
+ font-family: var(--font--family--latin);
13
13
  }
@@ -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), sans-serif;
13
+ font-family: var(--font--family--latin);
14
14
 
15
15
  &.fa {
16
- font-family: var(--font--family--arabic), var(--font--family), sans-serif;
16
+ font-family: var(--font--family--arabic);
17
17
  }
18
18
  }
19
19
 
@@ -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)', zIndex: 2 },
82
- { transform: 'translateY(10%)', offset: 0.5, zIndex: 2 },
83
- { transform: 'translateY(0)', zIndex: 1 },
83
+ { transform: 'translateY(0)' },
84
+ { transform: 'translateY(10%)', offset: 0.5 },
85
+ { transform: 'translateY(0)' },
84
86
  ];
@@ -8,6 +8,16 @@
8
8
  }
9
9
  }
10
10
 
11
+ @keyframes show {
12
+ 0% {
13
+ opacity: 0;
14
+ }
15
+
16
+ 100% {
17
+ opacity: 1;
18
+ }
19
+ }
20
+
11
21
  @keyframes progress {
12
22
  0% {
13
23
  clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
@@ -53,6 +53,6 @@ h4 {
53
53
  }
54
54
 
55
55
  [lang='fa-IR'] {
56
- --font--family: 'Vazirmatn', 'Open Sans', sans-serif;
57
- --font--family--title: 'Vazirmatn', 'Lato', sans-serif;
56
+ --font--family: 'Vazirmatn', sans-serif;
57
+ --font--family--title: 'Vazirmatn', sans-serif;
58
58
  }
@@ -27,7 +27,7 @@ $media-expressions: (
27
27
  right: 0;
28
28
  bottom: 0;
29
29
  left: 0;
30
- z-index: 1;
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
+ }