@scrabble-solver/scrabble-solver 2.11.0 → 2.11.2

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 (137) 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/277.js +523 -611
  16. package/.next/server/middleware-build-manifest.js +1 -1
  17. package/.next/server/pages/404.html +2 -2
  18. package/.next/server/pages/404.js.nft.json +1 -1
  19. package/.next/server/pages/500.html +1 -1
  20. package/.next/server/pages/_app.js +0 -24
  21. package/.next/server/pages/_app.js.nft.json +1 -1
  22. package/.next/server/pages/_document.js.nft.json +1 -1
  23. package/.next/server/pages/api/dictionary/[locale]/[word].js +0 -1
  24. package/.next/server/pages/api/dictionary/[locale].js +0 -1
  25. package/.next/server/pages/api/solve.js +28 -8
  26. package/.next/server/pages/api/verify.js +0 -1
  27. package/.next/server/pages/api/visit.js +0 -1
  28. package/.next/server/pages/index.html +1 -1
  29. package/.next/server/pages/index.js +49 -90
  30. package/.next/server/pages/index.js.nft.json +1 -1
  31. package/.next/server/pages/index.json +1 -1
  32. package/.next/static/Mdvi3FY0PqkILKLbPlVBU/_buildManifest.js +1 -0
  33. package/.next/static/chunks/pages/{404-e0f30450e9920dc3.js → 404-448ba28510855455.js} +1 -1
  34. package/.next/static/chunks/pages/_app-495e6f4ccc278bb2.js +28 -0
  35. package/.next/static/chunks/pages/index-5ecc51900ca29685.js +1 -0
  36. package/.next/static/css/17b0a2db8742105f.css +1 -0
  37. package/.next/static/css/e1ffeb2558330c55.css +2 -0
  38. package/.next/trace +54 -55
  39. package/package.json +9 -14
  40. package/src/components/{Solver/components/EmptyState/EmptyState.module.scss → Alert/Alert.module.scss} +11 -7
  41. package/src/components/{Solver/components/EmptyState/EmptyState.tsx → Alert/Alert.tsx} +8 -6
  42. package/src/components/Alert/index.ts +1 -0
  43. package/src/components/Board/Board.module.scss +55 -0
  44. package/src/components/Board/BoardPure.tsx +4 -0
  45. package/src/components/Board/components/Cell/Cell.module.scss +51 -2
  46. package/src/components/Board/components/Cell/Cell.tsx +12 -0
  47. package/src/components/Board/components/Cell/CellPure.tsx +12 -0
  48. package/src/components/Board/hooks/useGrid.ts +9 -26
  49. package/src/components/Board/lib/getPositionInGrid.ts +1 -1
  50. package/src/components/Dictionary/Dictionary.module.scss +4 -0
  51. package/src/components/DictionaryInput/DictionaryInput.module.scss +1 -0
  52. package/src/components/EmptyState/EmptyState.module.scss +2 -1
  53. package/src/components/EmptyState/EmptyState.tsx +1 -2
  54. package/src/components/Loading/Loading.module.scss +1 -1
  55. package/src/components/Logo/Logo.tsx +5 -0
  56. package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
  57. package/src/components/Modal/Modal.module.scss +14 -0
  58. package/src/components/Modal/Modal.tsx +4 -1
  59. package/src/components/PlainTiles/PlainTiles.module.scss +1 -1
  60. package/src/components/PlainTiles/Tile.tsx +3 -3
  61. package/src/components/Rack/Rack.module.scss +25 -0
  62. package/src/components/Rack/Rack.tsx +5 -4
  63. package/src/components/Rack/RackTile.tsx +6 -13
  64. package/src/components/Results/HeaderButton.tsx +5 -12
  65. package/src/components/Results/Result.tsx +5 -3
  66. package/src/components/Results/Results.module.scss +41 -1
  67. package/src/components/Results/Results.tsx +29 -43
  68. package/src/components/Results/types.ts +1 -1
  69. package/src/components/ResultsInput/ResultsInput.module.scss +1 -0
  70. package/src/components/Solver/Solver.module.scss +9 -4
  71. package/src/components/Solver/Solver.tsx +17 -19
  72. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -6
  73. package/src/components/Solver/components/index.ts +0 -1
  74. package/src/components/Tile/Tile.module.scss +42 -61
  75. package/src/components/Tile/Tile.tsx +4 -3
  76. package/src/components/Tile/TilePure.tsx +4 -13
  77. package/src/components/Tooltip/Tooltip.module.scss +1 -72
  78. package/src/components/Tooltip/useTooltip.tsx +25 -35
  79. package/src/components/index.ts +1 -0
  80. package/src/hooks/index.ts +0 -1
  81. package/src/hooks/useAppLayout.ts +3 -1
  82. package/src/i18n/de.json +0 -1
  83. package/src/i18n/en.json +0 -1
  84. package/src/i18n/es.json +0 -1
  85. package/src/i18n/fa.json +0 -1
  86. package/src/i18n/fr.json +0 -1
  87. package/src/i18n/pl.json +0 -1
  88. package/src/lib/createRegExp.ts +13 -0
  89. package/src/lib/groupResults.ts +38 -0
  90. package/src/lib/guessLocale.ts +22 -0
  91. package/src/lib/index.ts +4 -2
  92. package/src/lib/sortResults.ts +6 -10
  93. package/src/modals/DictionaryModal/DictionaryModal.module.scss +6 -2
  94. package/src/modals/KeyMapModal/KeyMapModal.tsx +5 -21
  95. package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +4 -0
  96. package/src/modals/MenuModal/MenuModal.module.scss +7 -0
  97. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  98. package/src/modals/ResultsModal/ResultsModal.module.scss +3 -3
  99. package/src/modals/ResultsModal/ResultsModal.tsx +23 -11
  100. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +2 -0
  101. package/src/modals/WordsModal/WordsModal.module.scss +8 -1
  102. package/src/pages/api/dictionary/[locale]/[word].ts +0 -1
  103. package/src/pages/api/dictionary/[locale]/index.ts +0 -1
  104. package/src/pages/api/solve.ts +0 -1
  105. package/src/pages/api/verify.ts +0 -1
  106. package/src/pages/api/visit.ts +0 -1
  107. package/src/pages/index.tsx +13 -15
  108. package/src/parameters/index.ts +2 -2
  109. package/src/state/createAppStore.ts +26 -10
  110. package/src/state/sagas.ts +1 -0
  111. package/src/state/selectors.ts +37 -37
  112. package/src/state/slices/boardInitialState.ts +3 -1
  113. package/src/state/slices/cellFilterInitialState.ts +3 -3
  114. package/src/state/slices/cellFilterSlice.ts +3 -1
  115. package/src/state/slices/dictionaryInitialState.ts +2 -2
  116. package/src/state/slices/rackInitialState.ts +3 -1
  117. package/src/state/slices/resultsInitialState.ts +11 -4
  118. package/src/state/slices/settingsInitialState.ts +10 -21
  119. package/src/state/slices/solveInitialState.ts +2 -2
  120. package/src/state/slices/solveSlice.ts +2 -0
  121. package/src/state/slices/verifyInitialState.ts +13 -4
  122. package/src/state/types.ts +20 -2
  123. package/src/styles/mixins.scss +25 -1
  124. package/src/styles/variables.scss +17 -1
  125. package/src/types/index.ts +10 -2
  126. package/.next/static/45ye7793DY705HOcuK9lJ/_buildManifest.js +0 -1
  127. package/.next/static/chunks/pages/_app-d7acee5e526752d9.js +0 -28
  128. package/.next/static/chunks/pages/index-35d2c1c79a201ae2.js +0 -1
  129. package/.next/static/css/a48caa6f57de6e98.css +0 -1
  130. package/.next/static/css/c49bbe944ddd1b39.css +0 -2
  131. package/src/components/Board/types/index.ts +0 -4
  132. package/src/components/Solver/components/EmptyState/index.ts +0 -1
  133. package/src/components/Tooltip/constants.ts +0 -28
  134. package/src/hooks/useUniqueId.ts +0 -9
  135. package/src/lib/isCtrl.ts +0 -7
  136. package/src/state/rootReducer.ts +0 -25
  137. /package/.next/static/{45ye7793DY705HOcuK9lJ → Mdvi3FY0PqkILKLbPlVBU}/_ssgManifest.js +0 -0
@@ -4,7 +4,17 @@
4
4
  --background-color: transparent;
5
5
 
6
6
  position: relative;
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ padding: 0;
11
+ background-color: var(--background-color);
12
+ border-radius: var(--border--radius);
13
+ font-weight: bold;
14
+ text-transform: uppercase;
15
+ text-align: center;
7
16
  transition: var(--transition);
17
+ user-select: none;
8
18
 
9
19
  &.points1 {
10
20
  --background-color: var(--color--yellow);
@@ -23,13 +33,30 @@
23
33
  --background-color: var(--color--red);
24
34
  }
25
35
 
36
+ &.raised {
37
+ --shadow--size: 2px;
38
+ --shadow--blur: 2px;
39
+ --shadow--spread: -1px;
40
+ --shadow--color: rgba(34, 34, 34, 0.8);
41
+
42
+ box-shadow: inset calc(-1 * var(--shadow--size)) calc(-1 * var(--shadow--size)) var(--shadow--blur)
43
+ var(--shadow--spread) var(--shadow--color);
44
+
45
+ @include media('<xs') {
46
+ --shadow--size: 1px;
47
+ --shadow--spread: 0;
48
+ --shadow--blur: 1px;
49
+ }
50
+ }
51
+
26
52
  &.blank {
27
53
  --background-color: var(--color--white);
28
54
 
29
- .placeholder,
30
- .character {
31
- color: var(--color--foreground);
32
- }
55
+ color: var(--color--foreground);
56
+ }
57
+
58
+ &.empty {
59
+ color: var(--color--inactive);
33
60
  }
34
61
 
35
62
  &.highlighted {
@@ -37,76 +64,28 @@
37
64
 
38
65
  color: var(--color--primary--opposite);
39
66
 
40
- .placeholder,
41
- .character {
42
- color: inherit;
43
- }
44
-
45
67
  .points {
46
68
  color: inherit;
47
69
  }
48
70
  }
49
-
50
- &:not(.disabled) {
51
- .input::selection {
52
- --background--color: transparent;
53
- }
54
- }
55
- }
56
-
57
- .input,
58
- .character,
59
- .placeholder {
60
- padding: 0;
61
- font-weight: bold;
62
- text-transform: uppercase;
63
- text-align: center;
64
- caret-color: transparent;
65
- box-sizing: border-box;
66
71
  }
67
72
 
68
73
  .input {
69
- width: 100%;
70
- height: 100%;
71
- background-color: transparent;
72
- color: transparent;
73
- border: none;
74
- font-size: 16px; // prevent iOS from automatically zooming in on focus
75
- }
76
-
77
- .character,
78
- .placeholder {
79
74
  position: absolute;
80
75
  top: 0;
81
76
  right: 0;
82
77
  bottom: 0;
83
78
  left: 0;
84
- display: flex;
85
- align-items: center;
86
- justify-content: center;
87
- background-color: var(--background-color);
88
- transition: var(--transition);
89
- pointer-events: none;
90
- user-select: none;
91
-
92
- .empty & {
93
- color: var(--color--inactive);
94
- }
95
-
96
- .raised & {
97
- box-shadow: inset -2px -2px 2px -1px rgba(34, 34, 34, 0.8);
98
-
99
- @include media('<xs') {
100
- box-shadow: inset -2px -2px 1px -1px rgba(34, 34, 34, 0.8);
101
- }
102
- }
103
- }
104
-
105
- .character {
106
- opacity: 1;
79
+ width: 100%;
80
+ height: 100%;
81
+ border: none;
82
+ background-color: transparent;
83
+ color: transparent;
84
+ caret-color: transparent;
85
+ font-size: 16px; // prevent iOS from automatically zooming in on focus
107
86
 
108
- .empty & {
109
- opacity: 0;
87
+ &::selection {
88
+ --background--color: transparent;
110
89
  }
111
90
  }
112
91
 
@@ -146,10 +125,12 @@
146
125
  [dir='ltr'] & {
147
126
  top: 0;
148
127
  right: 0;
128
+ border-top-right-radius: inherit;
149
129
  }
150
130
 
151
131
  [dir='rtl'] & {
152
132
  bottom: 0;
153
133
  right: 0;
134
+ border-bottom-right-radius: inherit;
154
135
  }
155
136
  }
@@ -60,8 +60,10 @@ const Tile: FunctionComponent<Props> = ({
60
60
  const locale = useTypedSelector(selectLocale);
61
61
  const { animateTile, showTilePoints } = useAppLayout();
62
62
  const { pointsFontSize, tileFontSize, tileSize } = getTileSizes(size);
63
- const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
64
- const characterStyle = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
63
+ const style = useMemo(
64
+ () => ({ fontSize: tileFontSize, height: tileSize, width: tileSize }),
65
+ [tileSize, tileFontSize],
66
+ );
65
67
  const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
66
68
  const ref = useRef<HTMLInputElement>(null);
67
69
  const mergedRef = useMergeRefs(inputRef ? [ref, inputRef] : [ref]);
@@ -98,7 +100,6 @@ const Tile: FunctionComponent<Props> = ({
98
100
  autoFocus={autoFocus}
99
101
  canShowPoints={canShowPoints}
100
102
  character={character}
101
- characterStyle={characterStyle}
102
103
  className={className}
103
104
  disabled={disabled}
104
105
  highlighted={highlighted}
@@ -18,7 +18,6 @@ interface Props {
18
18
  autoFocus?: boolean;
19
19
  canShowPoints?: boolean;
20
20
  character?: string;
21
- characterStyle?: CSSProperties;
22
21
  className?: string;
23
22
  disabled?: boolean;
24
23
  highlighted?: boolean;
@@ -42,7 +41,6 @@ const TilePure: FunctionComponent<Props> = ({
42
41
  autoFocus,
43
42
  canShowPoints,
44
43
  character,
45
- characterStyle,
46
44
  className,
47
45
  disabled,
48
46
  highlighted,
@@ -66,15 +64,18 @@ const TilePure: FunctionComponent<Props> = ({
66
64
  [styles.disabled]: disabled,
67
65
  [styles.empty]: !character,
68
66
  [styles.highlighted]: highlighted,
69
- [styles.raised]: raised,
67
+ [styles.placeholder]: !character,
70
68
  [styles.points1]: points === 1,
71
69
  [styles.points2]: points === 2,
72
70
  [styles.points3]: points === 3,
73
71
  [styles.points4]: points === 4,
74
72
  [styles.points5]: typeof points === 'number' && points >= 5,
73
+ [styles.raised]: raised,
75
74
  })}
76
75
  style={style}
77
76
  >
77
+ {character || placeholder}
78
+
78
79
  <input
79
80
  aria-label={ariaLabel}
80
81
  autoCapitalize="none"
@@ -92,16 +93,6 @@ const TilePure: FunctionComponent<Props> = ({
92
93
  onKeyDown={onKeyDown}
93
94
  />
94
95
 
95
- {placeholder && (
96
- <div className={styles.placeholder} style={characterStyle} tabIndex={-1}>
97
- {placeholder}
98
- </div>
99
- )}
100
-
101
- <div className={styles.character} style={characterStyle} tabIndex={-1}>
102
- {character}
103
- </div>
104
-
105
96
  {canShowPoints && (
106
97
  <span className={styles.points} style={pointsStyle}>
107
98
  {pointsFormatted}
@@ -1,11 +1,3 @@
1
- $arrow-size: 4px;
2
-
3
- :export {
4
- ARROW_SIZE: $arrow-size;
5
- PREVENT_OVERFLOW: 10px;
6
- OFFSET: 2px;
7
- }
8
-
9
1
  .tooltip {
10
2
  padding: var(--spacing--s) var(--spacing--m);
11
3
  box-shadow: var(--box-shadow);
@@ -13,71 +5,8 @@ $arrow-size: 4px;
13
5
  background-color: var(--color--tooltip--background);
14
6
  color: var(--color--tooltip--foreground);
15
7
  z-index: var(--z-index--tooltip);
16
-
17
- &.top {
18
- .arrow {
19
- bottom: -$arrow-size;
20
-
21
- &::after {
22
- left: 0;
23
- bottom: 0;
24
- border-top-color: var(--color--tooltip--background);
25
- border-bottom: none;
26
- }
27
- }
28
- }
29
-
30
- &.right {
31
- .arrow {
32
- left: -$arrow-size;
33
-
34
- &::after {
35
- left: 0;
36
- top: 0;
37
- border-right-color: var(--color--tooltip--background);
38
- border-left: none;
39
- }
40
- }
41
- }
42
-
43
- &.bottom {
44
- .arrow {
45
- top: -$arrow-size;
46
-
47
- &::after {
48
- top: 0;
49
- left: 0;
50
- border-bottom-color: var(--color--tooltip--background);
51
- border-top: none;
52
- }
53
- }
54
- }
55
-
56
- &.left {
57
- .arrow {
58
- right: -$arrow-size;
59
-
60
- &::after {
61
- right: 0;
62
- top: 0;
63
- border-left-color: var(--color--tooltip--background);
64
- border-right: none;
65
- }
66
- }
67
- }
68
8
  }
69
9
 
70
10
  .arrow {
71
- position: absolute;
72
- width: 2 * $arrow-size;
73
- height: 2 * $arrow-size;
74
-
75
- &::after {
76
- content: ' ';
77
- position: absolute;
78
- height: 0;
79
- width: 0;
80
- pointer-events: none;
81
- border: $arrow-size solid transparent;
82
- }
11
+ fill: var(--color--tooltip--background);
83
12
  }
@@ -1,3 +1,4 @@
1
+ import { arrow, autoUpdate, flip, FloatingArrow, offset, shift, useFloating } from '@floating-ui/react';
1
2
  import classNames from 'classnames';
2
3
  import {
3
4
  FocusEvent,
@@ -6,16 +7,15 @@ import {
6
7
  MouseEventHandler,
7
8
  ReactNode,
8
9
  useCallback,
10
+ useId,
9
11
  useMemo,
12
+ useRef,
10
13
  useState,
11
14
  } from 'react';
12
- import { usePopper } from 'react-popper';
13
- import { useMountedState, useRafLoop } from 'react-use';
14
15
 
15
- import { useIsTouchDevice, usePortal, useUniqueId } from 'hooks';
16
+ import { useIsTouchDevice, usePortal } from 'hooks';
16
17
  import { noop } from 'lib';
17
18
 
18
- import { MODIFIERS } from './constants';
19
19
  import styles from './Tooltip.module.scss';
20
20
 
21
21
  interface Props {
@@ -36,29 +36,27 @@ interface TriggerProps {
36
36
  onMouseOver?: MouseEventHandler;
37
37
  }
38
38
 
39
+ const ARROW_SIZE = 7;
40
+ const ARROW_GAP = 3;
41
+ const PADDING = 10;
42
+
39
43
  // eslint-disable-next-line max-statements
40
44
  const useTooltip = (
41
45
  tooltip: ReactNode,
42
46
  { className, placement = 'top', onBlur = noop, onFocus = noop, onMouseOut = noop, onMouseOver = noop }: Props = {},
43
47
  ): TriggerProps => {
44
- const id = useUniqueId();
48
+ const id = useId();
45
49
  const isTouchDevice = useIsTouchDevice();
46
50
  const isEnabled = Boolean(tooltip) || tooltip === 0;
47
- const isMounted = useMountedState();
48
51
  const [isShown, setIsShown] = useState<boolean>(false);
49
- const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null);
50
- const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
51
- const [arrowElement, setArrowElement] = useState<HTMLElement | null>(null);
52
- const {
53
- attributes,
54
- styles: popperStyles,
55
- update,
56
- } = usePopper(referenceElement, popperElement, {
57
- modifiers: [{ name: 'arrow', options: { element: arrowElement } }, ...MODIFIERS],
52
+ const arrowRef = useRef(null);
53
+ const ariaAttributes = useMemo(() => (isShown ? { 'aria-describedby': id } : {}), [id, isShown]);
54
+
55
+ const { x, y, context, refs, strategy } = useFloating({
56
+ middleware: [flip(), shift({ padding: PADDING }), offset(ARROW_SIZE + ARROW_GAP), arrow({ element: arrowRef })],
58
57
  placement,
58
+ whileElementsMounted: autoUpdate,
59
59
  });
60
- const computedPlacement = attributes.popper ? attributes.popper['data-popper-placement'] : placement;
61
- const ariaAttributes = useMemo(() => (isShown ? { 'aria-describedby': id } : {}), [id, isShown]);
62
60
 
63
61
  const handleBlur = useCallback(
64
62
  (event: FocusEvent) => {
@@ -95,7 +93,7 @@ const useTooltip = (
95
93
  const mouseTriggerProps = useMemo(
96
94
  () => ({
97
95
  ...ariaAttributes,
98
- ref: setReferenceElement,
96
+ ref: refs.setReference,
99
97
  onBlur: handleBlur,
100
98
  onFocus: handleFocus,
101
99
  onMouseOut: handleMouseOut,
@@ -107,33 +105,25 @@ const useTooltip = (
107
105
  const touchTriggerProps = useMemo(
108
106
  () => ({
109
107
  ...ariaAttributes,
110
- ref: setReferenceElement,
108
+ ref: refs.setReference,
111
109
  }),
112
110
  [ariaAttributes],
113
111
  );
114
112
 
115
113
  const triggerProps = isTouchDevice ? touchTriggerProps : mouseTriggerProps;
116
114
 
117
- useRafLoop(() => {
118
- if (isMounted() && update) {
119
- update();
120
- }
121
- });
122
-
123
115
  usePortal(
124
116
  <div
125
- className={classNames(styles.tooltip, className, {
126
- [styles.top]: computedPlacement === 'top',
127
- [styles.right]: computedPlacement === 'right',
128
- [styles.bottom]: computedPlacement === 'bottom',
129
- [styles.left]: computedPlacement === 'left',
130
- })}
131
- ref={setPopperElement}
132
- style={popperStyles.popper}
133
- {...attributes.popper}
117
+ className={classNames(styles.tooltip, className)}
118
+ ref={refs.setFloating}
119
+ style={{
120
+ position: strategy,
121
+ top: y ?? 0,
122
+ left: x ?? 0,
123
+ }}
134
124
  >
135
125
  <div>{tooltip}</div>
136
- <div className={styles.arrow} ref={setArrowElement} style={popperStyles.arrow} />
126
+ <FloatingArrow className={styles.arrow} context={context} ref={arrowRef} />
137
127
  </div>,
138
128
  { disabled: !isEnabled || !isShown },
139
129
  );
@@ -1,3 +1,4 @@
1
+ export { default as Alert } from './Alert';
1
2
  export { default as Badge } from './Badge';
2
3
  export { default as Board } from './Board';
3
4
  export { default as Button } from './Button';
@@ -5,4 +5,3 @@ export { default as useLanguage } from './useLanguage';
5
5
  export { default as useLocalStorage } from './useLocalStorage';
6
6
  export { default as useMediaQuery } from './useMediaQuery';
7
7
  export { default as usePortal } from './usePortal';
8
- export { default as useUniqueId } from './useUniqueId';
@@ -1,4 +1,4 @@
1
- import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL } from 'parameters';
1
+ import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL, SOLVER_COLUMN_WIDTH } from 'parameters';
2
2
 
3
3
  import useIsTouchDevice from './useIsTouchDevice';
4
4
  import useMediaQuery from './useMediaQuery';
@@ -14,8 +14,10 @@ const useAppLayout = () => {
14
14
 
15
15
  return {
16
16
  animateTile: !isLessThanXs,
17
+ columnWidth: showColumn ? SOLVER_COLUMN_WIDTH : 0,
17
18
  componentsSpacing: isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING,
18
19
  isBoardFullWidth: isLessThanM,
20
+ isModalFullWidth: isLessThanS,
19
21
  showColumn,
20
22
  showCompactControls: !showColumn,
21
23
  showFloatingSolveButton: isTouchDevice,
package/src/i18n/de.json CHANGED
@@ -45,7 +45,6 @@
45
45
  "rack.tile.location": "Ablage: Stein ({{index}})",
46
46
  "remaining-tiles": "Restliche Steine",
47
47
  "results": "Ergebnisse",
48
- "results.empty-state.no-filtered-results": "Keine Ergebnisse für diese Anfrage.",
49
48
  "results.empty-state.no-results": "Keine Ergebnisse - kein Wort konnte generiert werden.",
50
49
  "results.empty-state.outdated": "Ergebnisse sind alt. Klicken zum Aktualisieren.",
51
50
  "results.empty-state.uninitialized": "Wörter die aus deinen Buchstaben generiert wurden erscheinen hier.",
package/src/i18n/en.json CHANGED
@@ -45,7 +45,6 @@
45
45
  "rack.tile.location": "Rack: tile ({{index}})",
46
46
  "remaining-tiles": "Remaining tiles",
47
47
  "results": "Results",
48
- "results.empty-state.no-filtered-results": "No result matches this query.",
49
48
  "results.empty-state.no-results": "No results - unable to generate any words.",
50
49
  "results.empty-state.outdated": "Results are outdated. Click below to update.",
51
50
  "results.empty-state.uninitialized": "Words generated from your letters will be shown here.",
package/src/i18n/es.json CHANGED
@@ -45,7 +45,6 @@
45
45
  "rack.tile.location": "Estante: espacio ({{index}})",
46
46
  "remaining-tiles": "Casillas restantes",
47
47
  "results": "Resultados",
48
- "results.empty-state.no-filtered-results": "Ningún resultado coincide con esta consulta.",
49
48
  "results.empty-state.no-results": "No hay resultados; no se pueden generar palabras",
50
49
  "results.empty-state.outdated": "Los resultados están desactualizados. Haga clic a continuación para actualizar.",
51
50
  "results.empty-state.uninitialized": "Aquí se mostrarán las palabras generadas a partir de sus letras.",
package/src/i18n/fa.json CHANGED
@@ -45,7 +45,6 @@
45
45
  "rack.tile.location": "({{index}}) کاشی: طاقچه",
46
46
  "remaining-tiles": "کاشی های باقی مانده",
47
47
  "results": "نتایج",
48
- "results.empty-state.no-filtered-results": "پاسخی یافت نشد.",
49
48
  "results.empty-state.no-results": "کلمه قابل استفاده پیدا نشد.",
50
49
  "results.empty-state.outdated": "نتایج به روز نیستند، برای بروز رسانی کلیک کنید.",
51
50
  "results.empty-state.uninitialized": "کلمات تولید شده از حروف شما اینجا نمایش داده خواهد شد.",
package/src/i18n/fr.json CHANGED
@@ -45,7 +45,6 @@
45
45
  "rack.tile.location": "Chevalet: la case ({{index}})",
46
46
  "remaining-tiles": "Cases restantes",
47
47
  "results": "Résultats",
48
- "results.empty-state.no-filtered-results": "Aucun résultat ne correspond à cette requête",
49
48
  "results.empty-state.no-results": "Pas de résultats - impossible de générer des mots.",
50
49
  "results.empty-state.outdated": "Les résultats sont dépassé. Cliquer ci-dessous pour mettre à jour.",
51
50
  "results.empty-state.uninitialized": "Les mots générés à partir de vos lettres seront affichés ici.",
package/src/i18n/pl.json CHANGED
@@ -45,7 +45,6 @@
45
45
  "rack.tile.location": "Stojak: płytka ({{index}})",
46
46
  "remaining-tiles": "Pozostałe płytki",
47
47
  "results": "Wyniki",
48
- "results.empty-state.no-filtered-results": "Żaden wynik nie pasuje do tej kwerendy.",
49
48
  "results.empty-state.no-results": "Brak wyników - nie można wygenerować żadnego słowa.",
50
49
  "results.empty-state.outdated": "Wyniki są nieaktualne. Kliknij poniżej, aby zaktualizować.",
51
50
  "results.empty-state.uninitialized": "Tu zostaną wyświetlone słowa wygenerowane z Twoich liter.",
@@ -0,0 +1,13 @@
1
+ const createRegExp = (input: string): RegExp => {
2
+ if (input.trim().length === 0) {
3
+ return /.*/;
4
+ }
5
+
6
+ try {
7
+ return new RegExp(input, 'gi');
8
+ } catch {
9
+ return /.*/;
10
+ }
11
+ };
12
+
13
+ export default createRegExp;
@@ -0,0 +1,38 @@
1
+ import { Result } from '@scrabble-solver/types';
2
+
3
+ import { Point } from 'types';
4
+
5
+ import createRegExp from './createRegExp';
6
+
7
+ interface GroupedResults {
8
+ matching: Result[];
9
+ other: Result[];
10
+ }
11
+
12
+ const groupResults = (
13
+ results: Result[] | undefined,
14
+ query: string,
15
+ cellFilter: Point[],
16
+ ): GroupedResults | undefined => {
17
+ if (typeof results === 'undefined') {
18
+ return results;
19
+ }
20
+
21
+ return results.reduce<GroupedResults>(
22
+ ({ matching, other }, result) => {
23
+ const matchesQuery = createRegExp(query).test(result.word);
24
+ const matchesCellFilter = cellFilter.every(({ x, y }) => {
25
+ return result.cells.some((cell) => cell.x === x && cell.y === y);
26
+ });
27
+ const isMatching = matchesQuery && matchesCellFilter;
28
+
29
+ return {
30
+ matching: isMatching ? [...matching, result] : matching,
31
+ other: isMatching ? other : [...other, result],
32
+ };
33
+ },
34
+ { matching: [], other: [] },
35
+ );
36
+ };
37
+
38
+ export default groupResults;
@@ -0,0 +1,22 @@
1
+ import { Locale } from '@scrabble-solver/types';
2
+
3
+ const guessLocale = (): Locale => {
4
+ if (!globalThis.navigator) {
5
+ return Locale.EN_US;
6
+ }
7
+
8
+ const locales = Object.values(Locale);
9
+ const exactMatch = locales.find((locale) => globalThis.navigator.language === locale);
10
+
11
+ if (exactMatch) {
12
+ return exactMatch;
13
+ }
14
+
15
+ const partialMatch = locales.find((locale) => {
16
+ return globalThis.navigator.language === locale.substring(0, 2);
17
+ });
18
+
19
+ return partialMatch || Locale.EN_US;
20
+ };
21
+
22
+ export default guessLocale;
package/src/lib/index.ts CHANGED
@@ -5,6 +5,7 @@ export { default as createGridOf } from './createGridOf';
5
5
  export { default as createKeyboardNavigation } from './createKeyboardNavigation';
6
6
  export { default as createKeyComparator } from './createKeyComparator';
7
7
  export { default as createNullMovingComparator } from './createNullMovingComparator';
8
+ export { default as createRegExp } from './createRegExp';
8
9
  export { default as createStringComparator } from './createStringComparator';
9
10
  export { default as detectLocale } from './detectLocale';
10
11
  export { default as extractCharacters } from './extractCharacters';
@@ -14,10 +15,11 @@ export { default as getCellSize } from './getCellSize';
14
15
  export { default as getRemainingTiles } from './getRemainingTiles';
15
16
  export { default as getRemainingTilesCount } from './getRemainingTilesCount';
16
17
  export { default as getRemainingTilesGroups } from './getRemainingTilesGroups';
17
- export { default as getTotalRemainingTilesCount } from './getTotalRemainingTilesCount';
18
18
  export { default as getTileSizes } from './getTileSizes';
19
+ export { default as getTotalRemainingTilesCount } from './getTotalRemainingTilesCount';
20
+ export { default as groupResults } from './groupResults';
21
+ export { default as guessLocale } from './guessLocale';
19
22
  export { default as inverseDirection } from './inverseDirection';
20
- export { default as isCtrl } from './isCtrl';
21
23
  export { default as isMac } from './isMac';
22
24
  export { default as isRegExp } from './isRegExp';
23
25
  export { default as isStringArray } from './isStringArray';
@@ -1,6 +1,6 @@
1
1
  import { Result } from '@scrabble-solver/types';
2
2
 
3
- import { Comparator, ResultColumn, SortDirection } from 'types';
3
+ import { Comparator, ResultColumn, Sort, SortDirection } from 'types';
4
4
 
5
5
  import createKeyComparator from './createKeyComparator';
6
6
  import reverseComparator from './reverseComparator';
@@ -15,20 +15,16 @@ const comparators: Record<ResultColumn, (locale: string) => Comparator<Result>>
15
15
  [ResultColumn.WordsCount]: (locale: string) => createKeyComparator('wordsCount', locale),
16
16
  };
17
17
 
18
- const sortResults = (
19
- results: Result[] | undefined,
20
- column: ResultColumn,
21
- sortDirection: SortDirection,
22
- locale: string,
23
- ): Result[] | undefined => {
18
+ const sortResults = (results: Result[] | undefined, sort: Sort, locale: string): Result[] | undefined => {
24
19
  if (typeof results === 'undefined') {
25
20
  return results;
26
21
  }
27
22
 
28
- const createComparator = comparators[column];
23
+ const createComparator = comparators[sort.column];
29
24
  const comparator = createComparator(locale);
30
- const finalComparator = sortDirection === SortDirection.Descending ? reverseComparator(comparator) : comparator;
31
- return [...results].sort(finalComparator);
25
+ const finalComparator = sort.direction === SortDirection.Descending ? reverseComparator(comparator) : comparator;
26
+ const sortedResults = [...results].sort(finalComparator);
27
+ return sortedResults;
32
28
  };
33
29
 
34
30
  export default sortResults;
@@ -4,15 +4,19 @@
4
4
  flex-direction: column;
5
5
  background-color: var(--color--background--element);
6
6
  border: var(--border);
7
- box-shadow: var(--box-shadow);
7
+ border-radius: var(--border--radius);
8
8
  }
9
9
 
10
10
  .dictionary {
11
11
  flex: 1;
12
- border-bottom: var(--border);
13
12
  max-height: initial;
13
+ border-bottom: var(--border);
14
+ border-top-left-radius: var(--border--radius);
15
+ border-top-right-radius: var(--border--radius);
14
16
  }
15
17
 
16
18
  .dictionaryInput {
17
19
  flex: 0 0 auto;
20
+ border-bottom-left-radius: var(--border--radius);
21
+ border-bottom-right-radius: var(--border--radius);
18
22
  }