scrabble-solver 2.11.8 → 2.12.0

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 (147) hide show
  1. package/lerna.json +1 -1
  2. package/package.json +14 -14
  3. package/packages/configs/build/literaki/literaki.js +7 -7
  4. package/packages/configs/build/scrabble/scrabble.js +7 -7
  5. package/packages/configs/package.json +3 -3
  6. package/packages/configs/src/literaki/literaki.ts +7 -7
  7. package/packages/configs/src/scrabble/scrabble.ts +8 -8
  8. package/packages/constants/package.json +1 -1
  9. package/packages/dictionaries/package-lock.json +10 -3
  10. package/packages/dictionaries/package.json +4 -4
  11. package/packages/logger/package-lock.json +227 -6
  12. package/packages/logger/package.json +1 -1
  13. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  14. package/packages/scrabble-solver/.next/build-manifest.json +7 -7
  15. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  16. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  17. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  18. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  19. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  20. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
  21. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  22. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  23. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  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/routes-manifest.json +1 -1
  27. package/packages/scrabble-solver/.next/server/chunks/131.js +153 -115
  28. package/packages/scrabble-solver/.next/server/chunks/277.js +1426 -718
  29. package/packages/scrabble-solver/.next/server/chunks/44.js +6 -3
  30. package/packages/scrabble-solver/.next/server/chunks/50.js +20 -78
  31. package/packages/scrabble-solver/.next/server/chunks/911.js +14 -14
  32. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  33. package/packages/scrabble-solver/.next/server/pages/404.html +1 -1
  34. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  35. package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
  36. package/packages/scrabble-solver/.next/server/pages/_app.js +16 -0
  37. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  38. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  39. package/packages/scrabble-solver/.next/server/pages/api/solve.js +43 -11
  40. package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
  41. package/packages/scrabble-solver/.next/server/pages/index.js +152 -11
  42. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  43. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  44. package/packages/scrabble-solver/.next/server/pages-manifest.json +2 -2
  45. package/packages/scrabble-solver/.next/static/chunks/pages/{404-ca203fa27afc37d8.js → 404-b4b5ce15153d4825.js} +1 -1
  46. package/packages/scrabble-solver/.next/static/chunks/pages/_app-bea4539a6b8042de.js +32 -0
  47. package/packages/scrabble-solver/.next/static/chunks/pages/index-4e8566409753e1c3.js +1 -0
  48. package/packages/scrabble-solver/.next/static/css/58053f9594647860.css +2 -0
  49. package/packages/scrabble-solver/.next/static/css/{c6e0e01f44fc0425.css → 60e8258da7362a1a.css} +1 -1
  50. package/packages/scrabble-solver/.next/static/fsjQvvJ13WNxBdMioL4sc/_buildManifest.js +1 -0
  51. package/packages/scrabble-solver/.next/trace +52 -50
  52. package/packages/scrabble-solver/package-lock.json +5138 -304
  53. package/packages/scrabble-solver/package.json +15 -12
  54. package/packages/scrabble-solver/src/components/Board/Board.module.scss +18 -4
  55. package/packages/scrabble-solver/src/components/Board/Board.tsx +145 -76
  56. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +32 -40
  57. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +6 -17
  58. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +36 -18
  59. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +12 -13
  60. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +53 -3
  61. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss +48 -0
  62. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.tsx +81 -0
  63. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/index.ts +1 -0
  64. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss +21 -0
  65. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +34 -0
  66. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/index.ts +1 -0
  67. package/packages/scrabble-solver/src/components/Board/components/index.ts +2 -0
  68. package/packages/scrabble-solver/src/components/Board/hooks/index.ts +4 -0
  69. package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +27 -0
  70. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingActions.ts +22 -0
  71. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingFocus.ts +10 -0
  72. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingInputPrompt.ts +19 -0
  73. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +2 -1
  74. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +2 -2
  75. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +6 -6
  76. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +98 -23
  77. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +22 -0
  78. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.tsx +89 -0
  79. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/index.ts +1 -0
  80. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss +11 -0
  81. package/packages/scrabble-solver/src/components/Rack/{RackTile.tsx → components/RackTile/RackTile.tsx} +47 -7
  82. package/packages/scrabble-solver/src/components/Rack/components/RackTile/index.ts +1 -0
  83. package/packages/scrabble-solver/src/components/Rack/components/index.ts +2 -0
  84. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss +0 -8
  85. package/packages/scrabble-solver/src/components/Results/Cell.tsx +4 -3
  86. package/packages/scrabble-solver/src/components/Results/Result.tsx +6 -2
  87. package/packages/scrabble-solver/src/components/Results/Results.module.scss +6 -0
  88. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +0 -20
  89. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +2 -4
  90. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -10
  91. package/packages/scrabble-solver/src/components/Solver/components/index.ts +0 -1
  92. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +1 -0
  93. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +8 -6
  94. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +8 -0
  95. package/packages/scrabble-solver/src/hooks/useAppLayout.ts +3 -1
  96. package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +8 -0
  97. package/packages/scrabble-solver/src/i18n/de.json +6 -1
  98. package/packages/scrabble-solver/src/i18n/en.json +6 -1
  99. package/packages/scrabble-solver/src/i18n/es.json +6 -1
  100. package/packages/scrabble-solver/src/i18n/fa.json +6 -1
  101. package/packages/scrabble-solver/src/i18n/fr.json +6 -1
  102. package/packages/scrabble-solver/src/i18n/pl.json +6 -1
  103. package/packages/scrabble-solver/src/icons/Keyboard.svg +4 -3
  104. package/packages/scrabble-solver/src/icons/KeyboardFill.svg +4 -0
  105. package/packages/scrabble-solver/src/icons/index.ts +1 -0
  106. package/packages/scrabble-solver/src/lib/extractCharacters.test.ts +26 -0
  107. package/packages/scrabble-solver/src/lib/extractCharacters.ts +11 -9
  108. package/packages/scrabble-solver/src/lib/extractCharactersByCase.test.ts +31 -0
  109. package/packages/scrabble-solver/src/lib/extractCharactersByCase.ts +31 -0
  110. package/packages/scrabble-solver/src/lib/index.ts +3 -1
  111. package/packages/scrabble-solver/src/lib/isUpperCase.ts +7 -0
  112. package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +5 -1
  113. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss +12 -0
  114. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +55 -0
  115. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/index.ts +1 -0
  116. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/lib.ts +13 -0
  117. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/types.ts +7 -0
  118. package/packages/scrabble-solver/src/modals/SettingsModal/components/index.ts +1 -0
  119. package/packages/scrabble-solver/src/state/localStorage.ts +10 -1
  120. package/packages/scrabble-solver/src/state/selectors.ts +2 -0
  121. package/packages/scrabble-solver/src/state/slices/settingsInitialState.ts +4 -1
  122. package/packages/scrabble-solver/src/state/slices/settingsSlice.ts +6 -1
  123. package/packages/scrabble-solver/src/styles/mixins.scss +1 -0
  124. package/packages/scrabble-solver/src/styles/variables.scss +1 -0
  125. package/packages/scrabble-solver/src/types/index.ts +7 -0
  126. package/packages/solver/build/getCellsScore.js +3 -2
  127. package/packages/solver/package-lock.json +10 -3
  128. package/packages/solver/package.json +5 -5
  129. package/packages/solver/src/getCellsScore.ts +3 -2
  130. package/packages/types/build/Config.d.ts +2 -0
  131. package/packages/types/build/Config.js +3 -0
  132. package/packages/types/build/ConfigJson.d.ts +2 -0
  133. package/packages/types/package.json +2 -2
  134. package/packages/types/src/Config.ts +5 -0
  135. package/packages/types/src/ConfigJson.ts +2 -0
  136. package/packages/word-definitions/package-lock.json +2 -2
  137. package/packages/word-definitions/package.json +2 -2
  138. package/packages/word-lists/package-lock.json +403 -11
  139. package/packages/word-lists/package.json +2 -2
  140. package/packages/scrabble-solver/.next/static/5ttGCAW8jcIKxpR8om9fK/_buildManifest.js +0 -1
  141. package/packages/scrabble-solver/.next/static/chunks/pages/_app-76a8840b6244d5a2.js +0 -28
  142. package/packages/scrabble-solver/.next/static/chunks/pages/index-6894f40e6cac9243.js +0 -1
  143. package/packages/scrabble-solver/.next/static/css/af871fef886ef5b7.css +0 -2
  144. package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +0 -7
  145. package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.tsx +0 -53
  146. package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/index.ts +0 -1
  147. /package/packages/scrabble-solver/.next/static/{5ttGCAW8jcIKxpR8om9fK → fsjQvvJ13WNxBdMioL4sc}/_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,48 @@
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
+ border-radius: 0;
32
+ }
33
+
34
+ .insert {
35
+ [dir='ltr'] & {
36
+ border-top-left-radius: 0;
37
+ border-bottom-left-radius: 0;
38
+ }
39
+
40
+ [dir='rtl'] & {
41
+ border-top-right-radius: 0;
42
+ border-bottom-right-radius: 0;
43
+ }
44
+ }
45
+
46
+ .insertIcon {
47
+ transform: scale(1.5);
48
+ }
@@ -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';
@@ -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;
@@ -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;
@@ -358,7 +359,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
358
359
 
359
360
  return [
360
361
  { activeIndex, direction, inputRefs },
361
- { onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
362
+ { insertValue, onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
362
363
  ];
363
364
  };
364
365
 
@@ -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 {
@@ -1,7 +1,21 @@
1
+ /* eslint-disable max-lines, max-statements */
2
+
3
+ import { FloatingPortal, autoUpdate, useFloating } from '@floating-ui/react';
1
4
  import classNames from 'classnames';
2
- import { ChangeEvent, ClipboardEvent, createRef, FunctionComponent, useCallback, useMemo, useRef } from 'react';
5
+ import {
6
+ ChangeEvent,
7
+ ClipboardEvent,
8
+ FunctionComponent,
9
+ createRef,
10
+ useCallback,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ } from 'react';
15
+ import useOnclickOutside from 'react-cool-onclickoutside';
3
16
  import { useDispatch } from 'react-redux';
4
17
 
18
+ import { useAppLayout } from 'hooks';
5
19
  import { LOCALE_FEATURES } from 'i18n';
6
20
  import {
7
21
  createArray,
@@ -12,10 +26,18 @@ import {
12
26
  isCtrl,
13
27
  zipCharactersAndTiles,
14
28
  } from 'lib';
15
- import { rackSlice, selectConfig, selectLocale, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
16
-
29
+ import {
30
+ rackSlice,
31
+ selectConfig,
32
+ selectInputMode,
33
+ selectLocale,
34
+ selectRack,
35
+ selectResultCandidateTiles,
36
+ useTypedSelector,
37
+ } from 'state';
38
+
39
+ import { InputPrompt, RackTile } from './components';
17
40
  import styles from './Rack.module.scss';
18
- import RackTile from './RackTile';
19
41
 
20
42
  interface Props {
21
43
  className?: string;
@@ -27,13 +49,24 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
27
49
  const config = useTypedSelector(selectConfig);
28
50
  const locale = useTypedSelector(selectLocale);
29
51
  const rack = useTypedSelector(selectRack);
52
+ const inputMode = useTypedSelector(selectInputMode);
53
+ const { rackHeight } = useAppLayout();
30
54
  const resultCandidateTiles = useTypedSelector(selectResultCandidateTiles);
31
55
  const tiles = useMemo(() => zipCharactersAndTiles(rack, resultCandidateTiles), [rack, resultCandidateTiles]);
32
56
  const tilesCount = tiles.length;
33
57
  const tilesRefs = useMemo(() => createArray(tilesCount).map(() => createRef<HTMLInputElement>()), [tilesCount]);
34
58
  const activeIndexRef = useRef<number>();
59
+ const [hasFocus, setHasFocus] = useState(false);
60
+ const [input, setInput] = useState('');
35
61
  const { direction } = LOCALE_FEATURES[locale];
36
62
  const { tileFontSize } = getTileSizes(tileSize);
63
+ const showInputPrompt = inputMode === 'touchscreen' && hasFocus;
64
+ const ref = useRef<HTMLDivElement>(null);
65
+
66
+ useOnclickOutside(() => setHasFocus(false), {
67
+ ignoreClass: [InputPrompt.styles.form, InputPrompt.styles.input],
68
+ refs: [ref],
69
+ });
37
70
 
38
71
  const changeActiveIndex = useCallback(
39
72
  (offset: number) => {
@@ -75,6 +108,16 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
75
108
  [changeActiveIndex, config, dispatch],
76
109
  );
77
110
 
111
+ const handleFocus = useCallback(() => {
112
+ setHasFocus(true);
113
+ floatingInputPrompt.refs.setPositionReference(ref.current);
114
+ const characters: string[] = rack.filter((character) => character !== null) as string[];
115
+ const uppercasedDigraphs = characters.map((character) => {
116
+ return character.length > 1 ? character.toLocaleUpperCase(locale) : character;
117
+ });
118
+ setInput(uppercasedDigraphs.join(''));
119
+ }, [rack, ref]);
120
+
78
121
  const handleKeyDown = useMemo(() => {
79
122
  return createKeyboardNavigation({
80
123
  onArrowLeft: (event) => {
@@ -114,26 +157,58 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
114
157
  });
115
158
  }, [changeActiveIndex, config, direction]);
116
159
 
160
+ const floatingInputPrompt = useFloating({
161
+ placement: 'bottom-start',
162
+ whileElementsMounted: autoUpdate,
163
+ });
164
+
117
165
  return (
118
- <div className={classNames(styles.rack, className)} style={{ fontSize: tileFontSize }} onPaste={handlePaste}>
119
- {tiles.map(({ character, tile }, index) => (
120
- <RackTile
121
- activeIndexRef={activeIndexRef}
122
- character={character}
123
- className={classNames({
124
- [styles.sharpLeft]: index !== 0,
125
- [styles.sharpRight]: index !== tiles.length - 1,
126
- })}
127
- index={index}
128
- inputRef={tilesRefs[index]}
129
- key={index}
130
- size={tileSize}
131
- tile={tile}
132
- onChange={handleChange}
133
- onKeyDown={handleKeyDown}
134
- />
135
- ))}
136
- </div>
166
+ <>
167
+ <div
168
+ className={classNames(styles.rack, className, {
169
+ [styles.hidden]: showInputPrompt,
170
+ })}
171
+ ref={ref}
172
+ style={{ fontSize: tileFontSize }}
173
+ onPaste={handlePaste}
174
+ >
175
+ {tiles.map(({ character, tile }, index) => (
176
+ <RackTile
177
+ activeIndexRef={activeIndexRef}
178
+ character={character}
179
+ className={classNames(styles.tile, {
180
+ [styles.sharpLeft]: index !== 0,
181
+ [styles.sharpRight]: index !== tiles.length - 1,
182
+ })}
183
+ index={index}
184
+ inputRef={tilesRefs[index]}
185
+ key={index}
186
+ size={tileSize}
187
+ tile={tile}
188
+ onChange={handleChange}
189
+ onKeyDown={handleKeyDown}
190
+ onFocus={handleFocus}
191
+ />
192
+ ))}
193
+ </div>
194
+
195
+ {showInputPrompt && (
196
+ <FloatingPortal>
197
+ <InputPrompt
198
+ ref={floatingInputPrompt.refs.setFloating}
199
+ style={{
200
+ position: floatingInputPrompt.strategy,
201
+ top: floatingInputPrompt.y ? floatingInputPrompt.y - rackHeight : 0,
202
+ left: floatingInputPrompt.x ?? 0,
203
+ }}
204
+ value={input}
205
+ onBlur={() => setHasFocus(false)}
206
+ onChange={(event) => setInput(event.target.value)}
207
+ onSubmit={() => setHasFocus(false)}
208
+ />
209
+ </FloatingPortal>
210
+ )}
211
+ </>
137
212
  );
138
213
  };
139
214
 
@@ -0,0 +1,22 @@
1
+ @import 'styles/mixins';
2
+
3
+ .form {
4
+ @include focus-effect;
5
+
6
+ border-radius: var(--border--radius);
7
+ }
8
+
9
+ .input {
10
+ @include text-input;
11
+
12
+ position: absolute;
13
+ top: 0;
14
+ right: 0;
15
+ bottom: 0;
16
+ left: 0;
17
+ width: 100%;
18
+ height: 100%;
19
+ border-radius: var(--border--radius);
20
+ border: none;
21
+ box-shadow: var(--box-shadow);
22
+ }