@scrabble-solver/scrabble-solver 2.15.21 → 2.15.22

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 (68) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +12 -12
  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/_app.js.nft.json +1 -1
  21. package/.next/server/pages/_error.js.nft.json +1 -1
  22. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  23. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  24. package/.next/server/pages/api/solve.js.nft.json +1 -1
  25. package/.next/server/pages/api/verify.js.nft.json +1 -1
  26. package/.next/server/pages/index.html +1 -1
  27. package/.next/server/pages/index.js +1 -1
  28. package/.next/server/pages/index.js.nft.json +1 -1
  29. package/.next/server/pages/index.json +1 -1
  30. package/.next/server/pages/not-found.html +1 -1
  31. package/.next/static/{8q_9JlEMMcIoT2TpStZYx → MxQ5rcf448WOpfCa2_VVz}/_buildManifest.js +1 -1
  32. package/.next/static/chunks/{framework-93e577820ffb186a.js → framework-906c47013125d174.js} +1 -1
  33. package/.next/static/chunks/pages/_app-6a68c941d55599c8.js +7 -0
  34. package/.next/static/chunks/pages/index-6c5b582e7afc063c.js +1 -0
  35. package/.next/static/chunks/webpack-1a7a166b34ed8d24.js +1 -0
  36. package/.next/trace +21 -21
  37. package/.next/trace-build +1 -1
  38. package/package.json +20 -20
  39. package/src/app-layout/AppLayoutContext.tsx +25 -0
  40. package/src/app-layout/index.ts +1 -0
  41. package/src/{hooks/useAppLayout.ts → app-layout/useAppLayoutValue.ts} +4 -6
  42. package/src/components/Board/Board.tsx +1 -1
  43. package/src/components/Board/components/InputPrompt/InputPrompt.tsx +9 -2
  44. package/src/components/Board/hooks/useBackgroundImage.tsx +2 -1
  45. package/src/components/Board/hooks/useBoardStyle.ts +1 -1
  46. package/src/components/Board/hooks/useFloatingActions.ts +1 -1
  47. package/src/components/Dictionary/Dictionary.tsx +1 -1
  48. package/src/components/DictionaryInput/DictionaryInput.tsx +2 -2
  49. package/src/components/Logo/Logo.tsx +1 -1
  50. package/src/components/NavButtons/NavButtons.tsx +1 -1
  51. package/src/components/Rack/Rack.tsx +1 -1
  52. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +4 -4
  53. package/src/components/Results/Header.tsx +2 -1
  54. package/src/components/Results/Result.tsx +2 -1
  55. package/src/components/ResultsInput/ResultsInput.tsx +2 -2
  56. package/src/components/Solver/Solver.tsx +2 -1
  57. package/src/components/Tile/Tile.tsx +1 -1
  58. package/src/hooks/index.ts +0 -1
  59. package/src/lib/isMac.ts +0 -1
  60. package/src/modals/ResultsModal/ResultsModal.tsx +1 -1
  61. package/src/pages/_app.tsx +7 -4
  62. package/src/pages/index.tsx +42 -21
  63. package/tsconfig.json +3 -2
  64. package/tsconfig.tsbuildinfo +1 -1
  65. package/.next/static/chunks/pages/_app-ac2ab0ef8f59ba92.js +0 -3
  66. package/.next/static/chunks/pages/index-3be9ce84ae513eaa.js +0 -1
  67. package/.next/static/chunks/webpack-49a83516933d4cf2.js +0 -1
  68. /package/.next/static/{8q_9JlEMMcIoT2TpStZYx → MxQ5rcf448WOpfCa2_VVz}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-typescript","duration":1730400,"timestamp":2793256903,"id":13,"parentId":1,"tags":{},"startTime":1775242438301,"traceId":"e97b27e6c0eb60ec"},{"name":"run-webpack","duration":5983835,"timestamp":2794989610,"id":15,"parentId":1,"tags":{},"startTime":1775242440034,"traceId":"e97b27e6c0eb60ec"},{"name":"static-check","duration":166818,"timestamp":2801042702,"id":2011,"parentId":1,"tags":{},"startTime":1775242446087,"traceId":"e97b27e6c0eb60ec"},{"name":"static-generation","duration":817182,"timestamp":2801217087,"id":2029,"parentId":1,"tags":{},"startTime":1775242446261,"traceId":"e97b27e6c0eb60ec"},{"name":"collect-build-traces","duration":3570908,"timestamp":2801209841,"id":2026,"parentId":1,"tags":{},"startTime":1775242446254,"traceId":"e97b27e6c0eb60ec"},{"name":"telemetry-flush","duration":20,"timestamp":2804781801,"id":2041,"parentId":1,"tags":{},"startTime":1775242449826,"traceId":"e97b27e6c0eb60ec"},{"name":"next-build","duration":11769581,"timestamp":2793012244,"id":1,"tags":{"buildMode":"default","version":"16.2.2","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1775242438057,"traceId":"e97b27e6c0eb60ec"}]
1
+ [{"name":"run-typescript","duration":1892271,"timestamp":10741325368,"id":13,"parentId":1,"tags":{},"startTime":1775397253884,"traceId":"142e972b1da52edc"},{"name":"run-webpack","duration":6166870,"timestamp":10743220826,"id":15,"parentId":1,"tags":{},"startTime":1775397255780,"traceId":"142e972b1da52edc"},{"name":"static-check","duration":176351,"timestamp":10749456101,"id":2016,"parentId":1,"tags":{},"startTime":1775397262015,"traceId":"142e972b1da52edc"},{"name":"static-generation","duration":820346,"timestamp":10749640404,"id":2034,"parentId":1,"tags":{},"startTime":1775397262199,"traceId":"142e972b1da52edc"},{"name":"collect-build-traces","duration":3624593,"timestamp":10749632918,"id":2031,"parentId":1,"tags":{},"startTime":1775397262192,"traceId":"142e972b1da52edc"},{"name":"telemetry-flush","duration":17,"timestamp":10753258582,"id":2046,"parentId":1,"tags":{},"startTime":1775397265817,"traceId":"142e972b1da52edc"},{"name":"next-build","duration":12173853,"timestamp":10741084749,"id":1,"tags":{"buildMode":"default","version":"16.2.2","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1775397253644,"traceId":"142e972b1da52edc"}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.15.21",
3
+ "version": "2.15.22",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,33 +25,33 @@
25
25
  "type-check": "tsc --noEmit"
26
26
  },
27
27
  "dependencies": {
28
- "@floating-ui/react": "^0.27.16",
28
+ "@floating-ui/react": "^0.27.19",
29
29
  "@kamilmielnik/trie": "^4.0.0",
30
- "@reduxjs/toolkit": "^2.11.1",
31
- "@scrabble-solver/configs": "^2.15.21",
32
- "@scrabble-solver/constants": "^2.15.21",
33
- "@scrabble-solver/dictionaries": "^2.15.21",
34
- "@scrabble-solver/logger": "^2.15.21",
35
- "@scrabble-solver/solver": "^2.15.21",
36
- "@scrabble-solver/types": "^2.15.21",
37
- "@scrabble-solver/word-definitions": "^2.15.21",
30
+ "@reduxjs/toolkit": "^2.11.2",
31
+ "@scrabble-solver/configs": "^2.15.22",
32
+ "@scrabble-solver/constants": "^2.15.22",
33
+ "@scrabble-solver/dictionaries": "^2.15.22",
34
+ "@scrabble-solver/logger": "^2.15.22",
35
+ "@scrabble-solver/solver": "^2.15.22",
36
+ "@scrabble-solver/types": "^2.15.22",
37
+ "@scrabble-solver/word-definitions": "^2.15.22",
38
38
  "classnames": "^2.5.1",
39
39
  "env-cmd": "^11.0.0",
40
40
  "include-media": "^2.0.0",
41
41
  "include-media-query-builder": "^1.1.0",
42
- "next": "^16.0.10",
42
+ "next": "^16.2.2",
43
43
  "normalize.css": "^8.0.1",
44
- "react": "^19.2.3",
44
+ "react": "^19.2.4",
45
45
  "react-cool-onclickoutside": "^1.7.0",
46
- "react-dom": "^19.2.3",
46
+ "react-dom": "^19.2.4",
47
47
  "react-highlight-words": "^0.21.0",
48
48
  "react-modal": "^3.16.3",
49
49
  "react-redux": "^9.2.0",
50
- "react-window": "^2.2.3",
50
+ "react-window": "^2.2.7",
51
51
  "redux-saga": "^1.4.2",
52
52
  "store2": "^2.14.4",
53
- "transliteration": "^2.3.5",
54
- "use-debounce": "^10.0.6",
53
+ "transliteration": "^2.6.1",
54
+ "use-debounce": "^10.1.1",
55
55
  "workbox-expiration": "^7.4.0",
56
56
  "workbox-precaching": "^7.4.0",
57
57
  "workbox-routing": "^7.4.0",
@@ -60,14 +60,14 @@
60
60
  },
61
61
  "devDependencies": {
62
62
  "@svgr/webpack": "^8.1.0",
63
- "@types/react": "^19.2.7",
63
+ "@types/react": "^19.2.14",
64
64
  "@types/react-dom": "^19.2.3",
65
- "@types/react-highlight-words": "^0.20.0",
65
+ "@types/react-highlight-words": "^0.20.1",
66
66
  "@types/react-modal": "^3.16.3",
67
67
  "@types/react-portal": "^4.0.7",
68
68
  "@types/react-redux": "^7.1.34",
69
69
  "@types/redux": "^3.6.31",
70
- "sass": "^1.96.0"
70
+ "sass": "^1.99.0"
71
71
  },
72
- "gitHead": "0bac5e768652a92a24dcfdb650aa43d02559f687"
72
+ "gitHead": "6d8b6b51e3cad36844454d3564b90b1e14e9fcf8"
73
73
  }
@@ -0,0 +1,25 @@
1
+ import { createContext, type FunctionComponent, type ReactNode, useContext } from 'react';
2
+
3
+ import { type AppLayout, useAppLayoutValue } from './useAppLayoutValue';
4
+
5
+ const AppLayoutContext = createContext<AppLayout | null>(null);
6
+
7
+ export const useAppLayout = () => {
8
+ const context = useContext(AppLayoutContext);
9
+
10
+ if (context === null) {
11
+ throw new Error('useAppLayout must be used within an <AppLayoutProvider />');
12
+ }
13
+
14
+ return context;
15
+ };
16
+
17
+ interface Props {
18
+ children: ReactNode;
19
+ }
20
+
21
+ export const AppLayoutProvider: FunctionComponent<Props> = ({ children }) => {
22
+ const appLayout = useAppLayoutValue();
23
+
24
+ return <AppLayoutContext.Provider value={appLayout}>{children}</AppLayoutContext.Provider>;
25
+ };
@@ -0,0 +1 @@
1
+ export { AppLayoutProvider, useAppLayout } from './AppLayoutContext';
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable max-statements */
2
2
 
3
+ import { useColumns, useIsTouchDevice, useMediaQueries, useViewportSize } from 'hooks';
3
4
  import {
4
5
  BOARD_TILE_SIZE_MAX,
5
6
  BORDER_WIDTH,
@@ -22,12 +23,7 @@ import {
22
23
  import { selectConfig, selectShowCoordinates, useTypedSelector } from 'state';
23
24
  import { type ResultColumnId } from 'types';
24
25
 
25
- import { useColumns } from './useColumns';
26
- import { useIsTouchDevice } from './useIsTouchDevice';
27
- import { useMediaQueries } from './useMediaQueries';
28
- import { useViewportSize } from './useViewportSize';
29
-
30
- export const useAppLayout = () => {
26
+ export const useAppLayoutValue = () => {
31
27
  const { viewportHeight, viewportWidth } = useViewportSize();
32
28
  const config = useTypedSelector(selectConfig);
33
29
  const showCoordinates = useTypedSelector(selectShowCoordinates);
@@ -112,3 +108,5 @@ export const useAppLayout = () => {
112
108
  tileSize,
113
109
  };
114
110
  };
111
+
112
+ export type AppLayout = ReturnType<typeof useAppLayoutValue>;
@@ -7,7 +7,7 @@ import { type CSSProperties, type FocusEventHandler, type FunctionComponent, use
7
7
  import useOnclickOutside from 'react-cool-onclickoutside';
8
8
  import { useDispatch } from 'react-redux';
9
9
 
10
- import { useAppLayout } from 'hooks';
10
+ import { useAppLayout } from 'app-layout';
11
11
  import { LOCALE_FEATURES } from 'i18n';
12
12
  import { TRANSITION } from 'parameters';
13
13
  import {
@@ -1,5 +1,12 @@
1
1
  import classNames from 'classnames';
2
- import { type FormEventHandler, forwardRef, type HTMLProps, type MouseEventHandler, useEffect, useState } from 'react';
2
+ import {
3
+ forwardRef,
4
+ type HTMLProps,
5
+ type MouseEventHandler,
6
+ type SubmitEventHandler,
7
+ useEffect,
8
+ useState,
9
+ } from 'react';
3
10
 
4
11
  import { Check } from 'icons';
5
12
  import { useTranslate } from 'state';
@@ -27,7 +34,7 @@ export const InputPrompt = forwardRef<HTMLFormElement, Props>(
27
34
  // On iOS it helps with losing focus too early which makes Actions disappear
28
35
  const handleMouseDown: MouseEventHandler = (event) => event.preventDefault();
29
36
 
30
- const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
37
+ const handleSubmit: SubmitEventHandler<HTMLFormElement> = (event) => {
31
38
  event.preventDefault();
32
39
  event.stopPropagation();
33
40
  onSubmit(input);
@@ -5,7 +5,8 @@ import { useMemo } from 'react';
5
5
  import { renderToString } from 'react-dom/server';
6
6
  import { Provider } from 'react-redux';
7
7
 
8
- import { useAppLayout, useMediaQueries } from 'hooks';
8
+ import { useAppLayout } from 'app-layout';
9
+ import { useMediaQueries } from 'hooks';
9
10
  import { LOCALE_FEATURES } from 'i18n';
10
11
  import { Star } from 'icons';
11
12
  import { getTileSizes } from 'lib';
@@ -1,6 +1,6 @@
1
1
  import { type CSSProperties, useMemo } from 'react';
2
2
 
3
- import { useAppLayout } from 'hooks';
3
+ import { useAppLayout } from 'app-layout';
4
4
  import { getTileSizes } from 'lib';
5
5
  import { BORDER_WIDTH } from 'parameters';
6
6
  import { selectConfig, selectShowCoordinates, useTypedSelector } from 'state';
@@ -1,6 +1,6 @@
1
1
  import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react';
2
2
 
3
- import { useAppLayout } from 'hooks';
3
+ import { useAppLayout } from 'app-layout';
4
4
  import { BOARD_CELL_ACTIONS_OFFSET } from 'parameters';
5
5
 
6
6
  export const useFloatingActions = () => {
@@ -1,7 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { type FunctionComponent } from 'react';
3
3
 
4
- import { useAppLayout } from 'hooks';
4
+ import { useAppLayout } from 'app-layout';
5
5
  import {
6
6
  selectDictionaryError,
7
7
  selectDictionaryIsLoading,
@@ -1,5 +1,5 @@
1
1
  import classNames from 'classnames';
2
- import { type ChangeEvent, type FormEvent, type FunctionComponent } from 'react';
2
+ import { type ChangeEvent, type FunctionComponent, type SubmitEvent } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
5
  import { LOCALE_FEATURES } from 'i18n';
@@ -22,7 +22,7 @@ export const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
22
22
  dispatch(dictionarySlice.actions.changeInput(event.target.value));
23
23
  };
24
24
 
25
- const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
25
+ const handleSubmit = (event: SubmitEvent<HTMLFormElement>) => {
26
26
  event.preventDefault();
27
27
  dispatch(dictionarySlice.actions.submit());
28
28
  };
@@ -1,7 +1,7 @@
1
1
  import Image from 'next/image';
2
2
  import { forwardRef } from 'react';
3
3
 
4
- import { useAppLayout } from 'hooks';
4
+ import { useAppLayout } from 'app-layout';
5
5
  import { LOGO_SRC } from 'parameters';
6
6
 
7
7
  interface Props {
@@ -1,7 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { type FunctionComponent, memo } from 'react';
3
3
 
4
- import { useAppLayout } from 'hooks';
4
+ import { useAppLayout } from 'app-layout';
5
5
  import { CardChecklist, Cog, Eraser, Github, KeyboardFill, List, Sack } from 'icons';
6
6
  import { GITHUB_PROJECT_URL } from 'parameters';
7
7
  import { selectConfig, useTranslate, useTypedSelector } from 'state';
@@ -16,7 +16,7 @@ import {
16
16
  import useOnclickOutside from 'react-cool-onclickoutside';
17
17
  import { useDispatch } from 'react-redux';
18
18
 
19
- import { useAppLayout } from 'hooks';
19
+ import { useAppLayout } from 'app-layout';
20
20
  import { LOCALE_FEATURES } from 'i18n';
21
21
  import { createKeyboardNavigation, extractCharacters, extractInputValue, getTileSizes, isCtrl } from 'lib';
22
22
  import { rackSlice, selectConfig, selectInputMode, selectLocale, selectRack, useTypedSelector } from 'state';
@@ -2,7 +2,7 @@ import classNames from 'classnames';
2
2
  import {
3
3
  type CSSProperties,
4
4
  type ChangeEventHandler,
5
- type FormEventHandler,
5
+ type SubmitEventHandler,
6
6
  forwardRef,
7
7
  useCallback,
8
8
  useEffect,
@@ -10,7 +10,7 @@ import {
10
10
  } from 'react';
11
11
  import { useDispatch } from 'react-redux';
12
12
 
13
- import { useAppLayout } from 'hooks';
13
+ import { useAppLayout } from 'app-layout';
14
14
  import { rackSlice, selectConfig, useTranslate, useTypedSelector } from 'state';
15
15
 
16
16
  import styles from './InputPrompt.module.scss';
@@ -22,7 +22,7 @@ interface Props {
22
22
  value: string;
23
23
  onBlur: () => void;
24
24
  onChange: ChangeEventHandler<HTMLInputElement>;
25
- onSubmit: FormEventHandler<HTMLFormElement>;
25
+ onSubmit: SubmitEventHandler<HTMLFormElement>;
26
26
  }
27
27
 
28
28
  const InputPromptBase = forwardRef<HTMLFormElement, Props>(
@@ -33,7 +33,7 @@ const InputPromptBase = forwardRef<HTMLFormElement, Props>(
33
33
  const config = useTypedSelector(selectConfig);
34
34
  const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
35
35
 
36
- const handleSubmit: FormEventHandler<HTMLFormElement> = useCallback(
36
+ const handleSubmit: SubmitEventHandler<HTMLFormElement> = useCallback(
37
37
  (event) => {
38
38
  event.preventDefault();
39
39
  const characters = extractRack(config, value);
@@ -1,6 +1,7 @@
1
1
  import { type FunctionComponent } from 'react';
2
2
 
3
- import { useAppLayout, useColumns } from 'hooks';
3
+ import { useAppLayout } from 'app-layout';
4
+ import { useColumns } from 'hooks';
4
5
  import { GeoAlt, OneTwoThree, Square, SquareA, SquareB, Squares, Words } from 'icons';
5
6
  import { RESULTS_COLUMN_WIDTH } from 'parameters';
6
7
  import { ResultColumnId } from 'types';
@@ -3,7 +3,8 @@ import { type FocusEventHandler, type MouseEventHandler, type ReactElement, useR
3
3
  import Highlighter from 'react-highlight-words';
4
4
  import { type RowComponentProps } from 'react-window';
5
5
 
6
- import { useAppLayout, useColumns } from 'hooks';
6
+ import { useAppLayout } from 'app-layout';
7
+ import { useColumns } from 'hooks';
7
8
  import { LOCALE_FEATURES } from 'i18n';
8
9
  import { noop } from 'lib';
9
10
  import {
@@ -1,5 +1,5 @@
1
1
  import classNames from 'classnames';
2
- import { type ChangeEvent, type FormEventHandler, type FunctionComponent, useState } from 'react';
2
+ import { type ChangeEvent, type FunctionComponent, type SubmitEventHandler, useState } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
5
  import { isRegExp } from 'lib';
@@ -27,7 +27,7 @@ export const ResultsInput: FunctionComponent<Props> = ({ className }) => {
27
27
  }
28
28
  };
29
29
 
30
- const handleSubmit: FormEventHandler = (event) => {
30
+ const handleSubmit: SubmitEventHandler = (event) => {
31
31
  event.preventDefault();
32
32
  };
33
33
 
@@ -3,7 +3,8 @@ import classNames from 'classnames';
3
3
  import { type FunctionComponent, memo, type SyntheticEvent, useEffect, useMemo } from 'react';
4
4
  import { useDispatch } from 'react-redux';
5
5
 
6
- import { useAppLayout, useIsTouchDevice } from 'hooks';
6
+ import { useAppLayout } from 'app-layout';
7
+ import { useIsTouchDevice } from 'hooks';
7
8
  import {
8
9
  resultsSlice,
9
10
  selectAreResultsOutdated,
@@ -13,7 +13,7 @@ import {
13
13
  useRef,
14
14
  } from 'react';
15
15
 
16
- import { useAppLayout } from 'hooks';
16
+ import { useAppLayout } from 'app-layout';
17
17
  import { getTileSizes, noop } from 'lib';
18
18
  import { selectLocale, useTypedSelector } from 'state';
19
19
 
@@ -1,4 +1,3 @@
1
- export { useAppLayout } from './useAppLayout';
2
1
  export { useColumns } from './useColumns';
3
2
  export { useDirection } from './useDirection';
4
3
  export { useEffectOnce } from './useEffectOnce';
package/src/lib/isMac.ts CHANGED
@@ -3,6 +3,5 @@ export const isMac = (): boolean => {
3
3
  return false;
4
4
  }
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/no-deprecated
7
6
  return globalThis.navigator.platform.startsWith('Mac') || globalThis.navigator.platform === 'iPhone';
8
7
  };
@@ -2,8 +2,8 @@ import { type Result } from '@scrabble-solver/types';
2
2
  import { type FunctionComponent, memo, useEffect, useMemo } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
+ import { useAppLayout } from 'app-layout';
5
6
  import { Button, Dictionary, Modal, Results } from 'components';
6
- import { useAppLayout } from 'hooks';
7
7
  import { Check, EyeFill } from 'icons';
8
8
  import { resultsSlice, selectProcessedResults, selectResultCandidate, useTranslate, useTypedSelector } from 'state';
9
9
 
@@ -4,6 +4,7 @@ import Head from 'next/head';
4
4
  import { type FunctionComponent } from 'react';
5
5
  import { Provider } from 'react-redux';
6
6
 
7
+ import { AppLayoutProvider } from 'app-layout';
7
8
  import { SeoMessage } from 'components';
8
9
  import { store } from 'state';
9
10
 
@@ -77,11 +78,13 @@ const App: FunctionComponent<AppProps> = ({ Component, pageProps }) => (
77
78
  </Head>
78
79
 
79
80
  <Provider store={store}>
80
- <SeoMessage />
81
+ <AppLayoutProvider>
82
+ <SeoMessage />
81
83
 
82
- <FloatingDelayGroup delay={0}>
83
- <Component {...pageProps} />
84
- </FloatingDelayGroup>
84
+ <FloatingDelayGroup delay={0}>
85
+ <Component {...pageProps} />
86
+ </FloatingDelayGroup>
87
+ </AppLayoutProvider>
85
88
  </Provider>
86
89
  </>
87
90
  );
@@ -1,7 +1,9 @@
1
+ /* eslint-disable max-statements */
2
+
1
3
  import { isObject } from '@scrabble-solver/types';
2
4
  import fs from 'fs';
3
5
  import path from 'path';
4
- import { type FunctionComponent, useState } from 'react';
6
+ import { type FunctionComponent, useCallback, useState } from 'react';
5
7
  import ReactModal from 'react-modal';
6
8
  import { useDispatch } from 'react-redux';
7
9
 
@@ -45,9 +47,26 @@ const Index: FunctionComponent<Props> = ({ version }) => {
45
47
  words: false,
46
48
  });
47
49
 
48
- const patchModals = (patch: Partial<Record<Modal, boolean>>) => {
50
+ const patchModals = useCallback((patch: Partial<Record<Modal, boolean>>) => {
49
51
  setModals((current) => ({ ...current, ...patch }));
50
- };
52
+ }, []);
53
+
54
+ const handleClear = useCallback(() => dispatch(reset()), [dispatch]);
55
+
56
+ const handleShowDictionary = useCallback(() => patchModals({ dictionary: true }), [patchModals]);
57
+ const handleShowKeyMap = useCallback(() => patchModals({ keyMap: true }), [patchModals]);
58
+ const handleShowMenu = useCallback(() => patchModals({ menu: true }), [patchModals]);
59
+ const handleShowRemainingTiles = useCallback(() => patchModals({ remainingTiles: true }), [patchModals]);
60
+ const handleShowResults = useCallback(() => patchModals({ results: true }), [patchModals]);
61
+ const handleShowSettings = useCallback(() => patchModals({ settings: true }), [patchModals]);
62
+ const handleShowWords = useCallback(() => patchModals({ words: true }), [patchModals]);
63
+ const handleCloseDictionary = useCallback(() => patchModals({ dictionary: false }), [patchModals]);
64
+ const handleCloseKeyMap = useCallback(() => patchModals({ keyMap: false }), [patchModals]);
65
+ const handleCloseMenu = useCallback(() => patchModals({ menu: false }), [patchModals]);
66
+ const handleCloseRemainingTiles = useCallback(() => patchModals({ remainingTiles: false }), [patchModals]);
67
+ const handleCloseResults = useCallback(() => patchModals({ results: false }), [patchModals]);
68
+ const handleCloseSettings = useCallback(() => patchModals({ settings: false }), [patchModals]);
69
+ const handleCloseWords = useCallback(() => patchModals({ words: false }), [patchModals]);
51
70
 
52
71
  useDirection(LOCALE_FEATURES[locale].direction);
53
72
  useLanguage(locale);
@@ -78,40 +97,42 @@ const Index: FunctionComponent<Props> = ({ version }) => {
78
97
  </div>
79
98
 
80
99
  <NavButtons
81
- onClear={() => dispatch(reset())}
82
- onShowKeyMap={() => patchModals({ keyMap: true })}
83
- onShowMenu={() => patchModals({ menu: true })}
84
- onShowRemainingTiles={() => patchModals({ remainingTiles: true })}
85
- onShowSettings={() => patchModals({ settings: true })}
86
- onShowWords={() => patchModals({ words: true })}
100
+ onClear={handleClear}
101
+ onShowKeyMap={handleShowKeyMap}
102
+ onShowMenu={handleShowMenu}
103
+ onShowRemainingTiles={handleShowRemainingTiles}
104
+ onShowSettings={handleShowSettings}
105
+ onShowWords={handleShowWords}
87
106
  />
88
107
  </div>
89
108
  </nav>
90
109
 
91
- <Solver className={styles.solver} onShowResults={() => patchModals({ results: true })} />
110
+ <main>
111
+ <Solver className={styles.solver} onShowResults={handleShowResults} />
112
+ </main>
92
113
 
93
114
  <MenuModal
94
115
  isOpen={modals.menu}
95
- onClose={() => patchModals({ menu: false })}
96
- onShowDictionary={() => patchModals({ dictionary: true })}
97
- onShowRemainingTiles={() => patchModals({ remainingTiles: true })}
98
- onShowSettings={() => patchModals({ settings: true })}
99
- onShowWords={() => patchModals({ words: true })}
116
+ onClose={handleCloseMenu}
117
+ onShowDictionary={handleShowDictionary}
118
+ onShowRemainingTiles={handleShowRemainingTiles}
119
+ onShowSettings={handleShowSettings}
120
+ onShowWords={handleShowWords}
100
121
  />
101
122
 
102
- <SettingsModal isOpen={modals.settings} onClose={() => patchModals({ settings: false })} />
123
+ <SettingsModal isOpen={modals.settings} onClose={handleCloseSettings} />
103
124
 
104
- <KeyMapModal isOpen={modals.keyMap} onClose={() => patchModals({ keyMap: false })} />
125
+ <KeyMapModal isOpen={modals.keyMap} onClose={handleCloseKeyMap} />
105
126
 
106
- <WordsModal isOpen={modals.words} onClose={() => patchModals({ words: false })} />
127
+ <WordsModal isOpen={modals.words} onClose={handleCloseWords} />
107
128
 
108
129
  {config.supportsRemainingTiles && (
109
- <RemainingTilesModal isOpen={modals.remainingTiles} onClose={() => patchModals({ remainingTiles: false })} />
130
+ <RemainingTilesModal isOpen={modals.remainingTiles} onClose={handleCloseRemainingTiles} />
110
131
  )}
111
132
 
112
- <ResultsModal isOpen={modals.results} onClose={() => patchModals({ results: false })} />
133
+ <ResultsModal isOpen={modals.results} onClose={handleCloseResults} />
113
134
 
114
- <DictionaryModal isOpen={modals.dictionary} onClose={() => patchModals({ dictionary: false })} />
135
+ <DictionaryModal isOpen={modals.dictionary} onClose={handleCloseDictionary} />
115
136
  </>
116
137
  );
117
138
  };
package/tsconfig.json CHANGED
@@ -11,6 +11,7 @@
11
11
  "noEmit": true,
12
12
  "paths": {
13
13
  "api": ["./src/api"],
14
+ "app-layout": ["./src/app-layout"],
14
15
  "components": ["./src/components"],
15
16
  "hooks": ["./src/hooks"],
16
17
  "icons": ["./src/icons"],
@@ -28,10 +29,10 @@
28
29
  "resolveJsonModule": true,
29
30
  "rootDir": "./src",
30
31
  "skipLibCheck": true,
31
- "typeRoots": ["./node_modules/@types", "./src/@types"],
32
+ "typeRoots": ["./node_modules/@types", "../../node_modules/@types", "./src/@types"],
32
33
  "incremental": true
33
34
  },
34
- "exclude": [ "node_modules", "**/*.test.ts"],
35
+ "exclude": ["node_modules", "**/*.test.ts"],
35
36
  "files": ["./src/@types/scss.d.ts", "./src/@types/svg.d.ts"],
36
37
  "include": ["next-env.d.ts", "./src"]
37
38
  }