@scrabble-solver/scrabble-solver 2.11.7 → 2.11.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 (122) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +7 -7
  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/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/131.js +153 -115
  16. package/.next/server/chunks/277.js +1430 -691
  17. package/.next/server/chunks/44.js +3 -0
  18. package/.next/server/chunks/50.js +20 -78
  19. package/.next/server/chunks/865.js +153 -115
  20. package/.next/server/chunks/911.js +14 -14
  21. package/.next/server/middleware-build-manifest.js +1 -1
  22. package/.next/server/pages/404.html +1 -1
  23. package/.next/server/pages/404.js.nft.json +1 -1
  24. package/.next/server/pages/500.html +1 -1
  25. package/.next/server/pages/_app.js +8 -0
  26. package/.next/server/pages/_app.js.nft.json +1 -1
  27. package/.next/server/pages/api/solve.js +44 -11
  28. package/.next/server/pages/index.html +1 -1
  29. package/.next/server/pages/index.js +169 -15
  30. package/.next/server/pages/index.js.nft.json +1 -1
  31. package/.next/server/pages/index.json +1 -1
  32. package/.next/static/9oRWxnZ1xFLSs55FJtiYi/_buildManifest.js +1 -0
  33. package/.next/static/chunks/pages/{404-ca203fa27afc37d8.js → 404-b4b5ce15153d4825.js} +1 -1
  34. package/.next/static/chunks/pages/_app-b0231bed954dd413.js +28 -0
  35. package/.next/static/chunks/pages/index-4e8566409753e1c3.js +1 -0
  36. package/.next/static/css/60e8258da7362a1a.css +1 -0
  37. package/.next/static/css/fcc46fec97b11afc.css +2 -0
  38. package/.next/trace +52 -50
  39. package/package.json +14 -13
  40. package/src/components/Board/Board.module.scss +18 -4
  41. package/src/components/Board/Board.tsx +145 -76
  42. package/src/components/Board/BoardPure.tsx +32 -40
  43. package/src/components/Board/components/Actions/Actions.module.scss +6 -17
  44. package/src/components/Board/components/Actions/Actions.tsx +36 -18
  45. package/src/components/Board/components/Cell/Cell.module.scss +12 -13
  46. package/src/components/Board/components/Cell/Cell.tsx +53 -3
  47. package/src/components/Board/components/InputPrompt/InputPrompt.module.scss +47 -0
  48. package/src/components/Board/components/InputPrompt/InputPrompt.tsx +81 -0
  49. package/src/components/Board/components/InputPrompt/index.ts +1 -0
  50. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss +21 -0
  51. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +34 -0
  52. package/src/components/Board/components/ToggleDirectionButton/index.ts +1 -0
  53. package/src/components/Board/components/index.ts +2 -0
  54. package/src/components/Board/hooks/index.ts +4 -0
  55. package/src/components/Board/hooks/useBackgroundImage.tsx +13 -9
  56. package/src/components/Board/hooks/useBoardStyle.ts +27 -0
  57. package/src/components/Board/hooks/useFloatingActions.ts +22 -0
  58. package/src/components/Board/hooks/useFloatingFocus.ts +10 -0
  59. package/src/components/Board/hooks/useFloatingInputPrompt.ts +19 -0
  60. package/src/components/Board/hooks/useGrid.ts +19 -2
  61. package/src/components/Key/Key.module.scss +7 -11
  62. package/src/components/NavButtons/NavButtons.tsx +2 -2
  63. package/src/components/Rack/Rack.module.scss +6 -6
  64. package/src/components/Rack/Rack.tsx +102 -24
  65. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +22 -0
  66. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +89 -0
  67. package/src/components/Rack/components/InputPrompt/index.ts +1 -0
  68. package/src/components/Rack/components/RackTile/RackTile.module.scss +11 -0
  69. package/src/components/Rack/{RackTile.tsx → components/RackTile/RackTile.tsx} +59 -9
  70. package/src/components/Rack/components/RackTile/index.ts +1 -0
  71. package/src/components/Rack/components/index.ts +2 -0
  72. package/src/components/Radio/Radio.module.scss +0 -8
  73. package/src/components/Solver/Solver.module.scss +0 -20
  74. package/src/components/Solver/Solver.tsx +2 -4
  75. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -10
  76. package/src/components/Solver/components/index.ts +0 -1
  77. package/src/components/Tile/Tile.module.scss +5 -0
  78. package/src/components/Tile/Tile.tsx +8 -6
  79. package/src/components/Tile/TilePure.tsx +8 -0
  80. package/src/hooks/useAppLayout.ts +3 -1
  81. package/src/hooks/useLocalStorage.ts +8 -0
  82. package/src/i18n/de.json +6 -1
  83. package/src/i18n/en.json +6 -1
  84. package/src/i18n/es.json +6 -1
  85. package/src/i18n/fa.json +6 -1
  86. package/src/i18n/fr.json +6 -1
  87. package/src/i18n/pl.json +6 -1
  88. package/src/icons/Keyboard.svg +4 -3
  89. package/src/icons/KeyboardFill.svg +4 -0
  90. package/src/icons/index.ts +1 -0
  91. package/src/lib/extractCharacters.test.ts +26 -0
  92. package/src/lib/extractCharacters.ts +11 -9
  93. package/src/lib/extractCharactersByCase.test.ts +31 -0
  94. package/src/lib/extractCharactersByCase.ts +31 -0
  95. package/src/lib/index.ts +4 -1
  96. package/src/lib/isCtrl.ts +7 -0
  97. package/src/lib/isUpperCase.ts +7 -0
  98. package/src/modals/KeyMapModal/KeyMapModal.tsx +20 -4
  99. package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +10 -4
  100. package/src/modals/SettingsModal/SettingsModal.tsx +5 -1
  101. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss +12 -0
  102. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +55 -0
  103. package/src/modals/SettingsModal/components/InputModeSetting/index.ts +1 -0
  104. package/src/modals/SettingsModal/components/InputModeSetting/lib.ts +13 -0
  105. package/src/modals/SettingsModal/components/InputModeSetting/types.ts +7 -0
  106. package/src/modals/SettingsModal/components/index.ts +1 -0
  107. package/src/state/localStorage.ts +10 -1
  108. package/src/state/selectors.ts +2 -0
  109. package/src/state/slices/settingsInitialState.ts +4 -1
  110. package/src/state/slices/settingsSlice.ts +6 -1
  111. package/src/styles/mixins.scss +1 -0
  112. package/src/styles/variables.scss +2 -0
  113. package/src/types/index.ts +7 -0
  114. package/.next/static/chunks/pages/_app-e89a3c225b87516a.js +0 -28
  115. package/.next/static/chunks/pages/index-58744f49bf6b891f.js +0 -1
  116. package/.next/static/css/34adfcf12a7d9bb6.css +0 -1
  117. package/.next/static/css/edaeaa48321b4cf2.css +0 -2
  118. package/.next/static/uhB6d-q63uRC6RubwepLq/_buildManifest.js +0 -1
  119. package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +0 -7
  120. package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.tsx +0 -53
  121. package/src/components/Solver/components/FloatingSolveButton/index.ts +0 -1
  122. /package/.next/static/{uhB6d-q63uRC6RubwepLq → 9oRWxnZ1xFLSs55FJtiYi}/_ssgManifest.js +0 -0
@@ -1,9 +1,24 @@
1
1
  import { EMPTY_CELL } from '@scrabble-solver/constants';
2
2
  import { Cell as CellModel } from '@scrabble-solver/types';
3
3
  import classNames from 'classnames';
4
- import { ChangeEventHandler, FunctionComponent, RefObject, useCallback } from 'react';
4
+ import {
5
+ ChangeEventHandler,
6
+ FocusEventHandler,
7
+ FunctionComponent,
8
+ MouseEventHandler,
9
+ RefObject,
10
+ TouchEventHandler,
11
+ useCallback,
12
+ } from 'react';
5
13
 
6
- import { selectCellIsValid, selectLocale, selectTilePoints, useTranslate, useTypedSelector } from 'state';
14
+ import {
15
+ selectCellIsValid,
16
+ selectInputMode,
17
+ selectLocale,
18
+ selectTilePoints,
19
+ useTranslate,
20
+ useTypedSelector,
21
+ } from 'state';
7
22
 
8
23
  import Tile from '../../../Tile';
9
24
 
@@ -37,11 +52,44 @@ const Cell: FunctionComponent<Props> = ({
37
52
  const { tile, x, y } = cell;
38
53
  const translate = useTranslate();
39
54
  const locale = useTypedSelector(selectLocale);
55
+ const inputMode = useTypedSelector(selectInputMode);
40
56
  const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
41
57
  const isValid = useTypedSelector((state) => selectCellIsValid(state, cell));
42
58
  const isEmpty = tile.character === EMPTY_CELL;
43
59
 
44
- const handleFocus = useCallback(() => onFocus(x, y), [x, y, onFocus]);
60
+ const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
61
+ (event) => {
62
+ if (inputMode === 'touchscreen') {
63
+ event.preventDefault();
64
+ event.target.blur();
65
+ }
66
+
67
+ onFocus(x, y);
68
+ },
69
+ [inputMode, onFocus, x, y],
70
+ );
71
+
72
+ const handleMouseDown: MouseEventHandler<HTMLInputElement> = useCallback(
73
+ (event) => {
74
+ if (inputMode === 'touchscreen') {
75
+ event.preventDefault();
76
+ }
77
+
78
+ onFocus(x, y);
79
+ },
80
+ [inputMode, onFocus, x, y],
81
+ );
82
+
83
+ const handleTouchStart: TouchEventHandler<HTMLInputElement> = useCallback(
84
+ (event) => {
85
+ if (inputMode === 'touchscreen') {
86
+ event.preventDefault();
87
+ }
88
+
89
+ onFocus(x, y);
90
+ },
91
+ [inputMode, onFocus, x, y],
92
+ );
45
93
 
46
94
  return (
47
95
  <Tile
@@ -66,6 +114,8 @@ const Cell: FunctionComponent<Props> = ({
66
114
  tabIndex={cell.x === 0 && cell.y === 0 ? undefined : -1}
67
115
  onChange={onChange}
68
116
  onFocus={handleFocus}
117
+ onMouseDown={handleMouseDown}
118
+ onTouchStart={handleTouchStart}
69
119
  />
70
120
  );
71
121
  };
@@ -0,0 +1,47 @@
1
+ @import 'styles/mixins';
2
+
3
+ .inputPrompt {
4
+ display: flex;
5
+ box-shadow: var(--box-shadow);
6
+ border-radius: var(--border--radius);
7
+ transition: var(--transition);
8
+ transition-property: opacity;
9
+ }
10
+
11
+ .toggleDirection {
12
+ [dir='ltr'] & {
13
+ border-top-right-radius: 0;
14
+ border-bottom-right-radius: 0;
15
+ }
16
+
17
+ [dir='rtl'] & {
18
+ border-top-left-radius: 0;
19
+ border-bottom-left-radius: 0;
20
+ }
21
+ }
22
+
23
+ .input {
24
+ @include text-input;
25
+
26
+ height: 100%;
27
+ max-width: 200px;
28
+ border: none;
29
+ border-top: var(--border);
30
+ border-bottom: var(--border);
31
+ }
32
+
33
+ .insert {
34
+ [dir='ltr'] & {
35
+ border-top-left-radius: 0;
36
+ border-bottom-left-radius: 0;
37
+ }
38
+
39
+ [dir='rtl'] & {
40
+ border-top-right-radius: 0;
41
+ border-bottom-right-radius: 0;
42
+ }
43
+ }
44
+
45
+ .insertIcon {
46
+ transform: scale(1.5);
47
+ }
@@ -0,0 +1,81 @@
1
+ import classNames from 'classnames';
2
+ import { FormEventHandler, forwardRef, HTMLProps, MouseEventHandler, useEffect, useState } from 'react';
3
+
4
+ import { Check } from 'icons';
5
+ import { useTranslate } from 'state';
6
+ import { Direction } from 'types';
7
+
8
+ import Button from '../../../Button';
9
+ import ToggleDirectionButton from '../ToggleDirectionButton';
10
+
11
+ import styles from './InputPrompt.module.scss';
12
+
13
+ interface Props extends Omit<HTMLProps<HTMLFormElement>, 'onSubmit'> {
14
+ className?: string;
15
+ direction: Direction;
16
+ initialValue: string;
17
+ onDirectionToggle: MouseEventHandler<HTMLButtonElement>;
18
+ onSubmit: (input: string) => void;
19
+ }
20
+
21
+ const InputPrompt = forwardRef<HTMLFormElement, Props>(
22
+ ({ className, direction, initialValue, onDirectionToggle, onSubmit, ...props }, ref) => {
23
+ const translate = useTranslate();
24
+ const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
25
+ const [input, setInput] = useState(initialValue.trim());
26
+
27
+ // On iOS it helps with losing focus too early which makes Actions disappear
28
+ const handleMouseDown: MouseEventHandler = (event) => event.preventDefault();
29
+
30
+ const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
31
+ event.preventDefault();
32
+ event.stopPropagation();
33
+ onSubmit(input);
34
+ };
35
+
36
+ useEffect(() => {
37
+ if (inputRef) {
38
+ inputRef.focus();
39
+ inputRef.select();
40
+ }
41
+ }, [inputRef]);
42
+
43
+ return (
44
+ <form className={classNames(styles.inputPrompt, className)} ref={ref} onSubmit={handleSubmit} {...props}>
45
+ <ToggleDirectionButton
46
+ className={styles.toggleDirection}
47
+ direction={direction}
48
+ onClick={onDirectionToggle}
49
+ onMouseDown={handleMouseDown}
50
+ />
51
+
52
+ <div>
53
+ <input
54
+ autoCapitalize="none"
55
+ autoComplete="off"
56
+ autoCorrect="off"
57
+ className={styles.input}
58
+ placeholder={translate('rack.placeholder')}
59
+ spellCheck={false}
60
+ ref={setInputRef}
61
+ value={input}
62
+ onChange={(event) => setInput(event.target.value)}
63
+ />
64
+ </div>
65
+
66
+ <Button
67
+ aria-label={translate('results.insert')}
68
+ className={styles.insert}
69
+ Icon={Check}
70
+ iconClassName={styles.insertIcon}
71
+ tooltip={translate('results.insert')}
72
+ type="submit"
73
+ variant="primary"
74
+ onMouseDown={handleMouseDown}
75
+ />
76
+ </form>
77
+ );
78
+ },
79
+ );
80
+
81
+ export default InputPrompt;
@@ -0,0 +1 @@
1
+ export { default } from './InputPrompt';
@@ -0,0 +1,21 @@
1
+ .button {
2
+ padding: var(--spacing--m);
3
+ box-shadow: none !important;
4
+
5
+ &:active,
6
+ &:hover {
7
+ color: var(--color--foreground);
8
+ }
9
+ }
10
+
11
+ .icon {
12
+ transition: var(--transition);
13
+
14
+ &.right {
15
+ transform: rotate(-90deg);
16
+
17
+ [dir='rtl'] & {
18
+ transform: rotate(90deg);
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,34 @@
1
+ import classNames from 'classnames';
2
+ import { ButtonHTMLAttributes, FunctionComponent } from 'react';
3
+
4
+ import { ArrowDown } from 'icons';
5
+ import { useTranslate } from 'state';
6
+ import { Direction } from 'types';
7
+
8
+ import Button from '../../../Button';
9
+
10
+ import styles from './ToggleDirectionButton.module.scss';
11
+
12
+ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
13
+ className?: string;
14
+ direction: Direction;
15
+ }
16
+
17
+ const ToggleDirectionButton: FunctionComponent<Props> = ({ className, direction, ...props }) => {
18
+ const translate = useTranslate();
19
+
20
+ return (
21
+ <Button
22
+ aria-label={translate('cell.toggle-direction')}
23
+ className={classNames(styles.button, className)}
24
+ Icon={ArrowDown}
25
+ iconClassName={classNames(styles.icon, {
26
+ [styles.right]: direction === 'horizontal',
27
+ })}
28
+ tooltip={translate('cell.toggle-direction')}
29
+ {...props}
30
+ />
31
+ );
32
+ };
33
+
34
+ export default ToggleDirectionButton;
@@ -0,0 +1 @@
1
+ export { default } from './ToggleDirectionButton';
@@ -1,2 +1,4 @@
1
1
  export { default as Actions } from './Actions';
2
2
  export { default as Cell } from './Cell';
3
+ export { default as InputPrompt } from './InputPrompt';
4
+ export { default as ToggleDirectionButton } from './ToggleDirectionButton';
@@ -1,2 +1,6 @@
1
1
  export { default as useBackgroundImage } from './useBackgroundImage';
2
+ export { default as useBoardStyle } from './useBoardStyle';
3
+ export { default as useFloatingActions } from './useFloatingActions';
4
+ export { default as useFloatingFocus } from './useFloatingFocus';
5
+ export { default as useFloatingInputPrompt } from './useFloatingInputPrompt';
2
6
  export { default as useGrid } from './useGrid';
@@ -5,7 +5,7 @@ import { useMemo } from 'react';
5
5
  import { renderToString } from 'react-dom/server';
6
6
  import { Provider } from 'react-redux';
7
7
 
8
- import { useAppLayout } from 'hooks';
8
+ import { useAppLayout, useMediaQueries } from 'hooks';
9
9
  import { FlagFill, Star } from 'icons';
10
10
  import { dataUrlToBlob, getTileSizes } from 'lib';
11
11
  import { BORDER_COLOR_LIGHT, BORDER_RADIUS, BORDER_WIDTH, COLOR_BONUS_START, COLOR_FILTERED } from 'parameters';
@@ -14,6 +14,8 @@ import { Point } from 'types';
14
14
 
15
15
  import { getBonusColor } from '../lib';
16
16
 
17
+ const BORDER_RADIUS_XS = 3;
18
+ const GRID_LINE_SIZE = 1;
17
19
  const HORIZONTAL_LINE = 'h';
18
20
  const VERTICAL_LINE = 'v';
19
21
  const BONUS = 'b';
@@ -23,6 +25,8 @@ const CELL_FILTER = 'c';
23
25
 
24
26
  const useBackgroundImage = () => {
25
27
  const { boardSize, cellSize } = useAppLayout();
28
+ const { isLessThanXs } = useMediaQueries();
29
+ const borderRadius = isLessThanXs ? BORDER_RADIUS_XS : BORDER_RADIUS;
26
30
  const config = useTypedSelector(selectConfig);
27
31
  const center = { x: Math.floor(config.boardWidth / 2), y: Math.floor(config.boardHeight / 2) };
28
32
  const cellFilter = useTypedSelector(selectCellFilter);
@@ -53,19 +57,19 @@ const useBackgroundImage = () => {
53
57
  >
54
58
  <defs>
55
59
  <symbol id={HORIZONTAL_LINE}>
56
- <rect fill={BORDER_COLOR_LIGHT} height={1} width={viewBoxWidth} />
60
+ <rect fill={BORDER_COLOR_LIGHT} height={GRID_LINE_SIZE} width={viewBoxWidth} />
57
61
  </symbol>
58
62
 
59
63
  <symbol id={VERTICAL_LINE}>
60
- <rect fill={BORDER_COLOR_LIGHT} height={viewBoxHeight} width={1} />
64
+ <rect fill={BORDER_COLOR_LIGHT} height={viewBoxHeight} width={GRID_LINE_SIZE} />
61
65
  </symbol>
62
66
 
63
67
  <symbol id={BONUS}>
64
- <rect height={bonusSize} rx={BORDER_RADIUS} width={bonusSize} x={bonusOffset} y={bonusOffset} />
68
+ <rect height={bonusSize} rx={borderRadius} width={bonusSize} x={bonusOffset} y={bonusOffset} />
65
69
  </symbol>
66
70
 
67
71
  <symbol id={BONUS_WORD_2}>
68
- <rect height={bonusSize} rx={BORDER_RADIUS} width={bonusSize} x={bonusOffset} y={bonusOffset} />
72
+ <rect height={bonusSize} rx={borderRadius} width={bonusSize} x={bonusOffset} y={bonusOffset} />
69
73
 
70
74
  <text
71
75
  dominantBaseline="central"
@@ -82,7 +86,7 @@ const useBackgroundImage = () => {
82
86
  </symbol>
83
87
 
84
88
  <symbol id={BONUS_WORD_3}>
85
- <rect height={bonusSize} rx={BORDER_RADIUS} width={bonusSize} x={bonusOffset} y={bonusOffset} />
89
+ <rect height={bonusSize} rx={borderRadius} width={bonusSize} x={bonusOffset} y={bonusOffset} />
86
90
 
87
91
  <text
88
92
  dominantBaseline="central"
@@ -102,7 +106,7 @@ const useBackgroundImage = () => {
102
106
  <rect
103
107
  fill={COLOR_FILTERED}
104
108
  height={bonusSize}
105
- rx={BORDER_RADIUS}
109
+ rx={borderRadius}
106
110
  width={bonusSize}
107
111
  x={bonusOffset}
108
112
  y={bonusOffset}
@@ -112,7 +116,7 @@ const useBackgroundImage = () => {
112
116
  </symbol>
113
117
  </defs>
114
118
 
115
- <rect fill="white" height={viewBoxHeight} rx={BORDER_RADIUS} width={viewBoxWidth} x="0" y="0" />
119
+ <rect fill="white" height={viewBoxHeight} rx={borderRadius} width={viewBoxWidth} x="0" y="0" />
116
120
 
117
121
  {Array.from({ length: config.boardHeight - 1 }).map((_value, index) => (
118
122
  <use key={index} href={`#${HORIZONTAL_LINE}`} y={(index + 1) * (cellSize + BORDER_WIDTH) - BORDER_WIDTH} />
@@ -137,7 +141,7 @@ const useBackgroundImage = () => {
137
141
  <rect
138
142
  fill={COLOR_BONUS_START}
139
143
  height={bonusSize}
140
- rx={BORDER_RADIUS}
144
+ rx={borderRadius}
141
145
  width={bonusSize}
142
146
  x={getX(center) + bonusOffset}
143
147
  y={getY(center) + bonusOffset}
@@ -0,0 +1,27 @@
1
+ import { CSSProperties, useMemo } from 'react';
2
+
3
+ import { useAppLayout } from 'hooks';
4
+ import { getTileSizes } from 'lib';
5
+ import { selectConfig, useTypedSelector } from 'state';
6
+
7
+ import useBackgroundImage from './useBackgroundImage';
8
+
9
+ const useBoardStyle = () => {
10
+ const config = useTypedSelector(selectConfig);
11
+ const { cellSize } = useAppLayout();
12
+ const { tileFontSize } = getTileSizes(cellSize);
13
+ const backgroundImage = useBackgroundImage();
14
+ const boardStyle = useMemo<CSSProperties>(
15
+ () => ({
16
+ backgroundImage,
17
+ fontSize: tileFontSize,
18
+ gridTemplateColumns: `repeat(${config.boardWidth}, 1fr)`,
19
+ gridTemplateRows: `repeat(${config.boardHeight}, 1fr)`,
20
+ }),
21
+ [backgroundImage, config.boardHeight, config.boardWidth, tileFontSize],
22
+ );
23
+
24
+ return boardStyle;
25
+ };
26
+
27
+ export default useBoardStyle;
@@ -0,0 +1,22 @@
1
+ import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react';
2
+
3
+ import { useAppLayout } from 'hooks';
4
+ import { BOARD_CELL_ACTIONS_OFFSET } from 'parameters';
5
+
6
+ const useFloatingActions = () => {
7
+ const { actionsWidth } = useAppLayout();
8
+
9
+ return useFloating({
10
+ middleware: [
11
+ offset({
12
+ mainAxis: -BOARD_CELL_ACTIONS_OFFSET,
13
+ alignmentAxis: BOARD_CELL_ACTIONS_OFFSET - actionsWidth,
14
+ }),
15
+ shift(),
16
+ ],
17
+ placement: 'top-end',
18
+ whileElementsMounted: autoUpdate,
19
+ });
20
+ };
21
+
22
+ export default useFloatingActions;
@@ -0,0 +1,10 @@
1
+ import { autoUpdate, useFloating } from '@floating-ui/react';
2
+
3
+ const useFloatingFocus = () => {
4
+ return useFloating({
5
+ placement: 'top-start',
6
+ whileElementsMounted: autoUpdate,
7
+ });
8
+ };
9
+
10
+ export default useFloatingFocus;
@@ -0,0 +1,19 @@
1
+ import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react';
2
+
3
+ import { BOARD_CELL_ACTIONS_OFFSET } from 'parameters';
4
+
5
+ const useFloatingInputPrompt = () => {
6
+ return useFloating({
7
+ middleware: [
8
+ offset({
9
+ mainAxis: -BOARD_CELL_ACTIONS_OFFSET,
10
+ alignmentAxis: BOARD_CELL_ACTIONS_OFFSET,
11
+ }),
12
+ shift(),
13
+ ],
14
+ placement: 'top',
15
+ whileElementsMounted: autoUpdate,
16
+ });
17
+ };
18
+
19
+ export default useFloatingInputPrompt;
@@ -17,7 +17,7 @@ import { useDispatch } from 'react-redux';
17
17
 
18
18
  import { useLatest } from 'hooks';
19
19
  import { LOCALE_FEATURES } from 'i18n';
20
- import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue } from 'lib';
20
+ import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
21
21
  import { boardSlice, selectConfig, selectLocale, useTypedSelector } from 'state';
22
22
  import { Direction, Point } from 'types';
23
23
 
@@ -32,6 +32,7 @@ interface State {
32
32
  }
33
33
 
34
34
  interface Actions {
35
+ insertValue: (position: Point, value: string) => void;
35
36
  onChange: ChangeEventHandler<HTMLInputElement>;
36
37
  onDirectionToggle: () => void;
37
38
  onFocus: (x: number, y: number) => void;
@@ -290,6 +291,22 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
290
291
 
291
292
  const { x, y } = position;
292
293
  const character = event.key.toLowerCase();
294
+ const isTogglingBlank = isCtrl(event) && character === 'b';
295
+ const twoCharacterTile = config.getTwoCharacterTileByPrefix(character);
296
+
297
+ if (isTogglingBlank) {
298
+ event.preventDefault();
299
+ dispatch(boardSlice.actions.toggleCellIsBlank(position));
300
+ return;
301
+ }
302
+
303
+ if (isCtrl(event) && twoCharacterTile) {
304
+ event.preventDefault();
305
+ dispatch(boardSlice.actions.changeCellValue({ x, y, value: twoCharacterTile }));
306
+ moveFocus(1);
307
+ return;
308
+ }
309
+
293
310
  const cell = rows[y][x];
294
311
  const twoCharacterCandidate = cell.tile.character + character;
295
312
 
@@ -342,7 +359,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
342
359
 
343
360
  return [
344
361
  { activeIndex, direction, inputRefs },
345
- { onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
362
+ { insertValue, onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
346
363
  ];
347
364
  };
348
365
 
@@ -1,19 +1,15 @@
1
- $key-size: 36px;
2
- $icon-size: 15px;
3
-
4
1
  .key {
5
2
  position: relative;
6
- top: calc(0px - 2 * var(--border--width));
7
3
  display: inline-block;
8
- min-width: $key-size;
9
- height: $key-size;
4
+ min-width: var(--key--height);
5
+ height: var(--key--height);
10
6
  padding: var(--spacing--s) var(--spacing--m);
11
7
  background-color: var(--color--white);
12
8
  border: var(--border);
13
9
  border-radius: var(--border--radius);
14
10
  box-shadow: var(--box-shadow);
15
11
  font-family: var(--font--family--monospace);
16
- line-height: calc(#{$key-size} - 2 * (var(--spacing--s) + var(--border--width)));
12
+ line-height: calc(var(--key--height) - 2 * (var(--spacing--s) + var(--border--width)));
17
13
  vertical-align: middle;
18
14
  text-align: center;
19
15
  white-space: nowrap;
@@ -24,9 +20,9 @@ $icon-size: 15px;
24
20
 
25
21
  :global(svg) {
26
22
  position: absolute;
27
- top: calc(#{($key-size - $icon-size) / 2} - var(--border--width));
28
- left: calc(#{($key-size - $icon-size) / 2} - var(--border--width));
29
- width: $icon-size;
30
- height: $icon-size;
23
+ top: calc((var(--key--height) - var(--key--icon--size)) / 2 - var(--border--width));
24
+ left: calc((var(--key--height) - var(--key--icon--size)) / 2 - var(--border--width));
25
+ width: var(--key--icon--size);
26
+ height: var(--key--icon--size);
31
27
  }
32
28
  }
@@ -2,7 +2,7 @@ import classNames from 'classnames';
2
2
  import { FunctionComponent, memo } from 'react';
3
3
 
4
4
  import { useAppLayout } from 'hooks';
5
- import { CardChecklist, Cog, Eraser, Github, Keyboard, List, Sack } from 'icons';
5
+ import { CardChecklist, Cog, Eraser, Github, KeyboardFill, List, Sack } from 'icons';
6
6
  import { GITHUB_PROJECT_URL } from 'parameters';
7
7
  import { selectHasInvalidWords, selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
8
8
 
@@ -117,7 +117,7 @@ const NavButtons: FunctionComponent<Props> = ({
117
117
  <IconButton
118
118
  aria-label={translate('keyMap')}
119
119
  className={styles.button}
120
- Icon={Keyboard}
120
+ Icon={KeyboardFill}
121
121
  tooltip={translate('keyMap')}
122
122
  onClick={onShowKeyMap}
123
123
  />
@@ -60,20 +60,20 @@
60
60
  }
61
61
 
62
62
  .rack {
63
+ position: relative;
63
64
  display: flex;
64
65
  box-shadow: var(--box-shadow);
65
66
  border-radius: var(--border--radius);
67
+ transition: var(--transition);
68
+ opacity: 1;
66
69
  }
67
70
 
68
71
  .tile {
69
- @include focus-effect;
70
72
  @include lighthouse-input-size-hack;
73
+ }
71
74
 
72
- --background-color: var(--color--background);
73
-
74
- &:focus-within {
75
- z-index: 2;
76
- }
75
+ .hidden {
76
+ opacity: 0;
77
77
  }
78
78
 
79
79
  .sharpLeft {