@scrabble-solver/scrabble-solver 2.15.4 → 2.15.6

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 (71) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +9 -9
  3. package/.next/cache/.rscinfo +1 -1
  4. package/.next/cache/.tsbuildinfo +1 -1
  5. package/.next/cache/eslint/.cache_8dgz12 +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/0.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  11. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  12. package/.next/cache/webpack/server-production/0.pack +0 -0
  13. package/.next/cache/webpack/server-production/index.pack +0 -0
  14. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  15. package/.next/diagnostics/framework.json +1 -1
  16. package/.next/next-minimal-server.js.nft.json +1 -1
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +1 -1
  19. package/.next/required-server-files.json +1 -1
  20. package/.next/routes-manifest.json +1 -1
  21. package/.next/server/chunks/{964.js → 292.js} +1 -1
  22. package/.next/server/chunks/331.js +2 -2
  23. package/.next/server/chunks/577.js +1 -1
  24. package/.next/server/chunks/807.js +1 -0
  25. package/.next/server/middleware-build-manifest.js +1 -1
  26. package/.next/server/pages/404.html +1 -1
  27. package/.next/server/pages/404.js.nft.json +1 -1
  28. package/.next/server/pages/500.html +1 -1
  29. package/.next/server/pages/_app.js +1 -1
  30. package/.next/server/pages/_app.js.nft.json +1 -1
  31. package/.next/server/pages/_document.js +1 -1
  32. package/.next/server/pages/_error.js +1 -1
  33. package/.next/server/pages/_error.js.nft.json +1 -1
  34. package/.next/server/pages/api/dictionary/[locale]/[word].js +2 -2
  35. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  36. package/.next/server/pages/api/dictionary/[locale].js +1 -1
  37. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  38. package/.next/server/pages/api/solve.js +1 -1
  39. package/.next/server/pages/api/solve.js.nft.json +1 -1
  40. package/.next/server/pages/api/verify.js +1 -1
  41. package/.next/server/pages/api/verify.js.nft.json +1 -1
  42. package/.next/server/pages/api/visit.js +1 -1
  43. package/.next/server/pages/index.html +1 -1
  44. package/.next/server/pages/index.js +1 -1
  45. package/.next/server/pages/index.js.nft.json +1 -1
  46. package/.next/server/pages/index.json +1 -1
  47. package/.next/server/pages-manifest.json +1 -1
  48. package/.next/static/chunks/main-8d63777bf8b90ccb.js +1 -0
  49. package/.next/static/chunks/pages/_app-79ef8056607a21da.js +1 -0
  50. package/.next/static/chunks/pages/index-5866a12bfb8f00c4.js +1 -0
  51. package/.next/static/css/bc158181051cf066.css +2 -0
  52. package/.next/static/{ySzTsF3RIcISdWO2C2H3Z → uekKIGQQUe4pM47TO6mBn}/_buildManifest.js +1 -1
  53. package/.next/trace +23 -23
  54. package/package.json +17 -17
  55. package/src/components/Board/Board.tsx +1 -0
  56. package/src/components/Button/Button.module.scss +16 -1
  57. package/src/components/Button/Button.tsx +3 -0
  58. package/src/components/Button/Link.tsx +3 -0
  59. package/src/components/Dictionary/Dictionary.module.scss +9 -1
  60. package/src/components/Dictionary/Dictionary.tsx +3 -0
  61. package/src/components/Radio/Radio.module.scss +14 -20
  62. package/src/components/Radio/Radio.tsx +3 -1
  63. package/src/hooks/useAppLayout.ts +3 -0
  64. package/src/modals/MenuModal/MenuModal.tsx +11 -3
  65. package/src/state/sagas.ts +3 -1
  66. package/.next/server/chunks/67.js +0 -1
  67. package/.next/static/chunks/main-6e8e32bd2f4e331c.js +0 -1
  68. package/.next/static/chunks/pages/_app-fb5bb6aa71eaaa4e.js +0 -1
  69. package/.next/static/chunks/pages/index-6ef7d0f12c9321e2.js +0 -1
  70. package/.next/static/css/35b36298aba4ccad.css +0 -2
  71. /package/.next/static/{ySzTsF3RIcISdWO2C2H3Z → uekKIGQQUe4pM47TO6mBn}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.15.4",
3
+ "version": "2.15.6",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -27,21 +27,21 @@
27
27
  "start": "env-cmd next start -p 3333"
28
28
  },
29
29
  "dependencies": {
30
- "@floating-ui/react": "^0.26.25",
30
+ "@floating-ui/react": "^0.26.27",
31
31
  "@kamilmielnik/trie": "^3.1.0",
32
32
  "@reduxjs/toolkit": "^2.3.0",
33
- "@scrabble-solver/configs": "^2.15.4",
34
- "@scrabble-solver/constants": "^2.15.4",
35
- "@scrabble-solver/dictionaries": "^2.15.4",
36
- "@scrabble-solver/logger": "^2.15.4",
37
- "@scrabble-solver/solver": "^2.15.4",
38
- "@scrabble-solver/types": "^2.15.4",
39
- "@scrabble-solver/word-definitions": "^2.15.4",
33
+ "@scrabble-solver/configs": "^2.15.6",
34
+ "@scrabble-solver/constants": "^2.15.6",
35
+ "@scrabble-solver/dictionaries": "^2.15.6",
36
+ "@scrabble-solver/logger": "^2.15.6",
37
+ "@scrabble-solver/solver": "^2.15.6",
38
+ "@scrabble-solver/types": "^2.15.6",
39
+ "@scrabble-solver/word-definitions": "^2.15.6",
40
40
  "classnames": "^2.5.1",
41
41
  "env-cmd": "^10.1.0",
42
42
  "include-media": "^2.0.0",
43
43
  "include-media-query-builder": "^1.1.0",
44
- "next": "^15.0.1",
44
+ "next": "^15.0.2",
45
45
  "normalize.css": "^8.0.1",
46
46
  "react": "^18.3.1",
47
47
  "react-cool-onclickoutside": "^1.7.0",
@@ -53,11 +53,11 @@
53
53
  "redux-saga": "^1.3.0",
54
54
  "store2": "^2.14.3",
55
55
  "use-debounce": "^10.0.4",
56
- "workbox-expiration": "^7.1.0",
57
- "workbox-precaching": "^7.1.0",
58
- "workbox-routing": "^7.1.0",
59
- "workbox-webpack-plugin": "^7.1.0",
60
- "workbox-window": "^7.1.0"
56
+ "workbox-expiration": "^7.3.0",
57
+ "workbox-precaching": "^7.3.0",
58
+ "workbox-routing": "^7.3.0",
59
+ "workbox-webpack-plugin": "^7.3.0",
60
+ "workbox-window": "^7.3.0"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@svgr/webpack": "^8.1.0",
@@ -71,7 +71,7 @@
71
71
  "@types/react-window": "^1.8.8",
72
72
  "@types/redux": "^3.6.31",
73
73
  "@types/redux-saga": "^0.10.5",
74
- "sass": "^1.80.3"
74
+ "sass": "^1.80.6"
75
75
  },
76
- "gitHead": "5e638b79c52e18a2852880d8ee91759b3e438144"
76
+ "gitHead": "5667158791e9edc65ae1e818302b230cf4ce1724"
77
77
  }
@@ -178,6 +178,7 @@ const Board: FunctionComponent<Props> = ({ className }) => {
178
178
  position: floatingFocus.strategy,
179
179
  top: floatingFocus.y + cellSize,
180
180
  left: floatingFocus.x,
181
+ display: floatingFocus.isPositioned ? 'block' : 'none',
181
182
  width: cellSize,
182
183
  height: cellSize,
183
184
  opacity: hasFocus ? 1 : 0,
@@ -60,8 +60,13 @@
60
60
  .content {
61
61
  display: flex;
62
62
  align-items: center;
63
+ justify-content: center;
63
64
  gap: var(--spacing--l);
64
65
  line-height: var(--button--icon--size);
66
+
67
+ @include media('<l') {
68
+ gap: var(--spacing--m);
69
+ }
65
70
  }
66
71
 
67
72
  .icon {
@@ -74,8 +79,18 @@
74
79
  .label {
75
80
  @include ellipsis;
76
81
 
77
- flex: 1;
78
82
  font-size: var(--font--size--h3);
79
83
  transition: var(--transition);
80
84
  text-align: start;
81
85
  }
86
+
87
+ .wide {
88
+ .content {
89
+ justify-content: normal;
90
+ gap: var(--spacing--l);
91
+ }
92
+
93
+ .label {
94
+ flex: 1;
95
+ }
96
+ }
@@ -12,6 +12,7 @@ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
12
12
  iconClassName?: string;
13
13
  tooltip?: ReactNode;
14
14
  variant?: 'default' | 'primary';
15
+ wide?: boolean;
15
16
  }
16
17
 
17
18
  const Button: FunctionComponent<Props> = ({
@@ -21,6 +22,7 @@ const Button: FunctionComponent<Props> = ({
21
22
  iconClassName,
22
23
  tooltip,
23
24
  variant = 'default',
25
+ wide,
24
26
  ...props
25
27
  }) => {
26
28
  return (
@@ -29,6 +31,7 @@ const Button: FunctionComponent<Props> = ({
29
31
  className={classNames(styles.button, className, {
30
32
  [styles.default]: variant === 'default',
31
33
  [styles.primary]: variant === 'primary',
34
+ [styles.wide]: wide,
32
35
  })}
33
36
  type="button"
34
37
  {...props}
@@ -12,6 +12,7 @@ interface Props extends AnchorHTMLAttributes<HTMLAnchorElement> {
12
12
  iconClassName?: string;
13
13
  tooltip?: ReactNode;
14
14
  variant?: 'default' | 'primary';
15
+ wide?: boolean;
15
16
  }
16
17
 
17
18
  const Link: FunctionComponent<Props> = ({
@@ -21,6 +22,7 @@ const Link: FunctionComponent<Props> = ({
21
22
  iconClassName,
22
23
  tooltip,
23
24
  variant = 'default',
25
+ wide,
24
26
  ...props
25
27
  }) => {
26
28
  return (
@@ -29,6 +31,7 @@ const Link: FunctionComponent<Props> = ({
29
31
  className={classNames(styles.button, className, {
30
32
  [styles.default]: variant === 'default',
31
33
  [styles.primary]: variant === 'primary',
34
+ [styles.wide]: wide,
32
35
  })}
33
36
  {...props}
34
37
  >
@@ -4,7 +4,6 @@
4
4
  position: relative;
5
5
  transition: var(--transition);
6
6
  word-break: break-word;
7
- overflow: hidden;
8
7
 
9
8
  &.isAllowed {
10
9
  background-color: var(--color--green--light);
@@ -13,6 +12,10 @@
13
12
  &.isNotAllowed {
14
13
  background-color: var(--color--red--light);
15
14
  }
15
+
16
+ &:focus-within {
17
+ @include focus-effect;
18
+ }
16
19
  }
17
20
 
18
21
  .content {
@@ -20,6 +23,11 @@
20
23
 
21
24
  height: 100%;
22
25
  overflow-y: auto;
26
+ border-radius: inherit;
27
+
28
+ &:focus-visible {
29
+ outline: none;
30
+ }
23
31
  }
24
32
 
25
33
  .result {
@@ -1,6 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent } from 'react';
3
3
 
4
+ import { useAppLayout } from 'hooks';
4
5
  import { selectDictionary, selectDictionaryError, useTranslate, useTypedSelector } from 'state';
5
6
 
6
7
  import EmptyState from '../EmptyState';
@@ -14,6 +15,7 @@ interface Props {
14
15
 
15
16
  const Dictionary: FunctionComponent<Props> = ({ className }) => {
16
17
  const translate = useTranslate();
18
+ const { dictionaryResultsHeight } = useAppLayout();
17
19
  const { results, isLoading } = useTypedSelector(selectDictionary);
18
20
  const error = useTypedSelector(selectDictionaryError);
19
21
  const isLastAllowed = results.at(-1)?.isAllowed;
@@ -24,6 +26,7 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
24
26
  [styles.isAllowed]: isLastAllowed === true,
25
27
  [styles.isNotAllowed]: isLastAllowed === false,
26
28
  })}
29
+ style={{ height: dictionaryResultsHeight }}
27
30
  >
28
31
  <div className={styles.content}>
29
32
  {typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}
@@ -16,10 +16,8 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
16
16
  cursor: pointer;
17
17
 
18
18
  &.checked {
19
- .icon {
20
- &::after {
21
- background-color: var(--color--primary);
22
- }
19
+ .iconContent {
20
+ background-color: var(--color--primary);
23
21
  }
24
22
  }
25
23
 
@@ -40,28 +38,24 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
40
38
  }
41
39
 
42
40
  .icon {
41
+ display: flex;
42
+ justify-content: center;
43
+ align-items: center;
43
44
  flex: 0 0 auto;
44
- position: relative;
45
45
  width: $radio-box-size;
46
46
  height: $radio-box-size;
47
- border-radius: 50%;
48
47
  border: $radio-inner-border solid var(--color--primary);
49
- pointer-events: none;
50
-
51
- &::after {
52
- content: ' ';
53
- position: absolute;
54
- top: 50%;
55
- left: 50%;
56
- transform: translate(-50%, -50%);
57
- width: $radio-inner-size;
58
- height: $radio-inner-size;
59
- background-color: transparent;
60
- border-radius: 50%;
61
- transition: var(--transition);
62
- }
48
+ border-radius: 50%;
63
49
  }
64
50
 
65
51
  .content {
66
52
  flex: 1;
67
53
  }
54
+
55
+ .iconContent {
56
+ width: $radio-inner-size;
57
+ height: $radio-inner-size;
58
+ background-color: transparent;
59
+ border-radius: 50%;
60
+ transition: var(--transition);
61
+ }
@@ -30,7 +30,9 @@ const Radio: FunctionComponent<Props> = ({ checked, children, className, disable
30
30
  onChange={onChange}
31
31
  />
32
32
 
33
- <div aria-hidden="true" className={styles.icon} role="img" />
33
+ <div aria-hidden="true" className={styles.icon} role="img">
34
+ <div className={styles.iconContent} />
35
+ </div>
34
36
 
35
37
  <div className={styles.content}>{children}</div>
36
38
  </label>
@@ -17,6 +17,7 @@ import {
17
17
  RACK_TILE_SIZE_MAX,
18
18
  RESULTS_COLUMN_WIDTH,
19
19
  SOLVER_COLUMN_WIDTH,
20
+ TEXT_INPUT_HEIGHT,
20
21
  } from 'parameters';
21
22
  import { selectConfig, selectShowCoordinates, useTypedSelector } from 'state';
22
23
  import { ResultColumnId } from 'types';
@@ -67,6 +68,7 @@ const useAppLayout = () => {
67
68
  const maxControlsWidth = tileSize * config.rackSize + 2 * BORDER_WIDTH;
68
69
  const showResultsInModal = isLessThanL;
69
70
  const dictionaryHeight = showResultsInModal ? DICTIONARY_HEIGHT_MOBILE : DICTIONARY_HEIGHT;
71
+ const dictionaryResultsHeight = dictionaryHeight - TEXT_INPUT_HEIGHT - 2 * BORDER_WIDTH;
70
72
  const modalWidth = isLessThanS ? viewportWidth : MODAL_WIDTH;
71
73
  const resultsHeight = isLessThanL
72
74
  ? viewportHeight - dictionaryHeight - BUTTON_HEIGHT - MODAL_HEADER_HEIGHT - 5 * componentsSpacing
@@ -86,6 +88,7 @@ const useAppLayout = () => {
86
88
  coordinatesFontSize: coordinatesSize * 0.6,
87
89
  coordinatesSize,
88
90
  dictionaryHeight,
91
+ dictionaryResultsHeight,
89
92
  isModalFullWidth: isLessThanS,
90
93
  logoHeight,
91
94
  logoWidth: logoHeight * LOGO_ASPECT_RATIO,
@@ -37,16 +37,23 @@ const MenuModal: FunctionComponent<Props> = ({
37
37
  aria-label={translate('remaining-tiles')}
38
38
  className={styles.button}
39
39
  Icon={Sack}
40
+ wide
40
41
  onClick={onShowRemainingTiles}
41
42
  >
42
43
  {translate('remaining-tiles')}
43
44
  </Button>
44
45
 
45
- <Button aria-label={translate('words')} className={styles.button} Icon={CardChecklist} onClick={onShowWords}>
46
+ <Button aria-label={translate('words')} className={styles.button} Icon={CardChecklist} wide onClick={onShowWords}>
46
47
  {translate('words')}
47
48
  </Button>
48
49
 
49
- <Button aria-label={translate('dictionary')} className={styles.button} Icon={BookHalf} onClick={onShowDictionary}>
50
+ <Button
51
+ aria-label={translate('dictionary')}
52
+ className={styles.button}
53
+ Icon={BookHalf}
54
+ wide
55
+ onClick={onShowDictionary}
56
+ >
50
57
  {translate('dictionary')}
51
58
  </Button>
52
59
 
@@ -57,11 +64,12 @@ const MenuModal: FunctionComponent<Props> = ({
57
64
  Icon={Github}
58
65
  rel="noopener noreferrer"
59
66
  target="_blank"
67
+ wide
60
68
  >
61
69
  {translate('github')}
62
70
  </Button.Link>
63
71
 
64
- <Button aria-label={translate('settings')} className={styles.button} Icon={Cog} onClick={onShowSettings}>
72
+ <Button aria-label={translate('settings')} className={styles.button} Icon={Cog} wide onClick={onShowSettings}>
65
73
  <div className={styles.settings}>
66
74
  <div className={styles.settingsLabel}>{translate('settings')}</div>
67
75
  <Icon aria-hidden="true" className={styles.flag} role="img" />
@@ -167,7 +167,9 @@ function* onLocaleChange({ payload: locale }: PayloadAction<Locale>): AnyGenerat
167
167
  function* onResultCandidateChange({ payload: result }: PayloadAction<Result | null>): AnyGenerator {
168
168
  if (result) {
169
169
  const locale: Locale = yield select(selectLocale);
170
- yield put(dictionarySlice.actions.changeInput(result.words.join(LOCALE_FEATURES[locale].separator)));
170
+ const uniqueWords = Array.from(new Set(result.words));
171
+ const input = uniqueWords.join(LOCALE_FEATURES[locale].separator);
172
+ yield put(dictionarySlice.actions.changeInput(input));
171
173
  yield put(dictionarySlice.actions.submit());
172
174
  }
173
175
  }