@scrabble-solver/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 (120) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +7 -7
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/131.js +153 -115
  16. package/.next/server/chunks/277.js +1426 -718
  17. package/.next/server/chunks/44.js +6 -3
  18. package/.next/server/chunks/50.js +20 -78
  19. package/.next/server/chunks/911.js +14 -14
  20. package/.next/server/middleware-build-manifest.js +1 -1
  21. package/.next/server/pages/404.html +1 -1
  22. package/.next/server/pages/404.js.nft.json +1 -1
  23. package/.next/server/pages/500.html +1 -1
  24. package/.next/server/pages/_app.js +16 -0
  25. package/.next/server/pages/_app.js.nft.json +1 -1
  26. package/.next/server/pages/_document.js.nft.json +1 -1
  27. package/.next/server/pages/api/solve.js +43 -11
  28. package/.next/server/pages/index.html +1 -1
  29. package/.next/server/pages/index.js +152 -11
  30. package/.next/server/pages/index.js.nft.json +1 -1
  31. package/.next/server/pages/index.json +1 -1
  32. package/.next/server/pages-manifest.json +2 -2
  33. package/.next/static/chunks/pages/{404-ca203fa27afc37d8.js → 404-b4b5ce15153d4825.js} +1 -1
  34. package/.next/static/chunks/pages/_app-bea4539a6b8042de.js +32 -0
  35. package/.next/static/chunks/pages/index-4e8566409753e1c3.js +1 -0
  36. package/.next/static/css/58053f9594647860.css +2 -0
  37. package/.next/static/css/{c6e0e01f44fc0425.css → 60e8258da7362a1a.css} +1 -1
  38. package/.next/static/fsjQvvJ13WNxBdMioL4sc/_buildManifest.js +1 -0
  39. package/.next/trace +52 -50
  40. package/package.json +16 -13
  41. package/src/components/Board/Board.module.scss +18 -4
  42. package/src/components/Board/Board.tsx +145 -76
  43. package/src/components/Board/BoardPure.tsx +32 -40
  44. package/src/components/Board/components/Actions/Actions.module.scss +6 -17
  45. package/src/components/Board/components/Actions/Actions.tsx +36 -18
  46. package/src/components/Board/components/Cell/Cell.module.scss +12 -13
  47. package/src/components/Board/components/Cell/Cell.tsx +53 -3
  48. package/src/components/Board/components/InputPrompt/InputPrompt.module.scss +48 -0
  49. package/src/components/Board/components/InputPrompt/InputPrompt.tsx +81 -0
  50. package/src/components/Board/components/InputPrompt/index.ts +1 -0
  51. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss +21 -0
  52. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +34 -0
  53. package/src/components/Board/components/ToggleDirectionButton/index.ts +1 -0
  54. package/src/components/Board/components/index.ts +2 -0
  55. package/src/components/Board/hooks/index.ts +4 -0
  56. package/src/components/Board/hooks/useBoardStyle.ts +27 -0
  57. package/src/components/Board/hooks/useFloatingActions.ts +22 -0
  58. package/src/components/Board/hooks/useFloatingFocus.ts +10 -0
  59. package/src/components/Board/hooks/useFloatingInputPrompt.ts +19 -0
  60. package/src/components/Board/hooks/useGrid.ts +2 -1
  61. package/src/components/NavButtons/NavButtons.tsx +2 -2
  62. package/src/components/Rack/Rack.module.scss +6 -6
  63. package/src/components/Rack/Rack.tsx +98 -23
  64. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +22 -0
  65. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +89 -0
  66. package/src/components/Rack/components/InputPrompt/index.ts +1 -0
  67. package/src/components/Rack/components/RackTile/RackTile.module.scss +11 -0
  68. package/src/components/Rack/{RackTile.tsx → components/RackTile/RackTile.tsx} +47 -7
  69. package/src/components/Rack/components/RackTile/index.ts +1 -0
  70. package/src/components/Rack/components/index.ts +2 -0
  71. package/src/components/Radio/Radio.module.scss +0 -8
  72. package/src/components/Results/Cell.tsx +4 -3
  73. package/src/components/Results/Result.tsx +6 -2
  74. package/src/components/Results/Results.module.scss +6 -0
  75. package/src/components/Solver/Solver.module.scss +0 -20
  76. package/src/components/Solver/Solver.tsx +2 -4
  77. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -10
  78. package/src/components/Solver/components/index.ts +0 -1
  79. package/src/components/Tile/Tile.module.scss +1 -0
  80. package/src/components/Tile/Tile.tsx +8 -6
  81. package/src/components/Tile/TilePure.tsx +8 -0
  82. package/src/hooks/useAppLayout.ts +3 -1
  83. package/src/hooks/useLocalStorage.ts +8 -0
  84. package/src/i18n/de.json +6 -1
  85. package/src/i18n/en.json +6 -1
  86. package/src/i18n/es.json +6 -1
  87. package/src/i18n/fa.json +6 -1
  88. package/src/i18n/fr.json +6 -1
  89. package/src/i18n/pl.json +6 -1
  90. package/src/icons/Keyboard.svg +4 -3
  91. package/src/icons/KeyboardFill.svg +4 -0
  92. package/src/icons/index.ts +1 -0
  93. package/src/lib/extractCharacters.test.ts +26 -0
  94. package/src/lib/extractCharacters.ts +11 -9
  95. package/src/lib/extractCharactersByCase.test.ts +31 -0
  96. package/src/lib/extractCharactersByCase.ts +31 -0
  97. package/src/lib/index.ts +3 -1
  98. package/src/lib/isUpperCase.ts +7 -0
  99. package/src/modals/SettingsModal/SettingsModal.tsx +5 -1
  100. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss +12 -0
  101. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +55 -0
  102. package/src/modals/SettingsModal/components/InputModeSetting/index.ts +1 -0
  103. package/src/modals/SettingsModal/components/InputModeSetting/lib.ts +13 -0
  104. package/src/modals/SettingsModal/components/InputModeSetting/types.ts +7 -0
  105. package/src/modals/SettingsModal/components/index.ts +1 -0
  106. package/src/state/localStorage.ts +10 -1
  107. package/src/state/selectors.ts +2 -0
  108. package/src/state/slices/settingsInitialState.ts +4 -1
  109. package/src/state/slices/settingsSlice.ts +6 -1
  110. package/src/styles/mixins.scss +1 -0
  111. package/src/styles/variables.scss +1 -0
  112. package/src/types/index.ts +7 -0
  113. package/.next/static/5ttGCAW8jcIKxpR8om9fK/_buildManifest.js +0 -1
  114. package/.next/static/chunks/pages/_app-76a8840b6244d5a2.js +0 -28
  115. package/.next/static/chunks/pages/index-6894f40e6cac9243.js +0 -1
  116. package/.next/static/css/af871fef886ef5b7.css +0 -2
  117. package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +0 -7
  118. package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.tsx +0 -53
  119. package/src/components/Solver/components/FloatingSolveButton/index.ts +0 -1
  120. /package/.next/static/{5ttGCAW8jcIKxpR8om9fK → fsjQvvJ13WNxBdMioL4sc}/_ssgManifest.js +0 -0
package/src/i18n/fa.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
+ "cell.enter-word": "کلمه را وارد کنید",
2
3
  "cell.filter-cell": "مقصد",
3
4
  "cell.set-blank": "علامت گذاری به عنوان خالی",
4
5
  "cell.set-not-blank": "علامت گذاری به عنوان غیر خالی",
@@ -22,7 +23,7 @@
22
23
  "dictionary.empty-state.no-results": "کلمه در فرهنگ لغت یافت نشد.",
23
24
  "dictionary.empty-state.not-allowed": "این کلمه مجاز نیست.",
24
25
  "dictionary.empty-state.uninitialized": "معنی لغت اینجا نمایش داده خواهد شد.",
25
- "dictionary.input.placeholder": "جستجو در فرهنگ لغت ...",
26
+ "dictionary.input.placeholder": "جستجو در فرهنگ لغت ",
26
27
  "dictionary.input.title": "کلمات جدا شده با کاما",
27
28
  "empty-state.error": "خطا",
28
29
  "empty-state.info": "اطلاعات",
@@ -43,6 +44,7 @@
43
44
  "menu": "منو",
44
45
  "rack.placeholder": "لیستحرف",
45
46
  "rack.tile.location": "({{index}}) کاشی: طاقچه",
47
+ "rack.touchscreen.placeholder": "لیستحرف…",
46
48
  "remaining-tiles": "کاشی های باقی مانده",
47
49
  "results": "نتایج",
48
50
  "results.empty-state.no-results": "کلمه قابل استفاده پیدا نشد.",
@@ -58,6 +60,9 @@
58
60
  "settings.autoGroupTiles.right": "در سمت راست",
59
61
  "settings.autoGroupTiles.null": "کنار هم قرار نده",
60
62
  "settings.game": "بازی",
63
+ "settings.inputMode": "حالت ورودی",
64
+ "settings.inputMode.keyboard": "صفحه کلید",
65
+ "settings.inputMode.touchscreen": "صفحه لمسی",
61
66
  "settings.language": "زبان",
62
67
  "words": "کلمات ساخته شده",
63
68
  "words.invalid": "نا معتبر",
package/src/i18n/fr.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
+ "cell.enter-word": "Entrez un mot",
2
3
  "cell.filter-cell": "Destination cible",
3
4
  "cell.set-blank": "Marquer comme vide",
4
5
  "cell.set-not-blank": "Marquer comme non vide",
@@ -22,7 +23,7 @@
22
23
  "dictionary.empty-state.no-results": "Impossible de trouver une définition pour ce mot dans le dictionaire.",
23
24
  "dictionary.empty-state.not-allowed": "Ce mot n'est pas pas acceptable.",
24
25
  "dictionary.empty-state.uninitialized": "La définition dictionaire sera affichée ici.",
25
- "dictionary.input.placeholder": "Rechercher dans le dictionnaire...",
26
+ "dictionary.input.placeholder": "Rechercher dans le dictionnaire",
26
27
  "dictionary.input.title": "Mots séparées par des virgules",
27
28
  "empty-state.error": "Erreur",
28
29
  "empty-state.info": "Info",
@@ -43,6 +44,7 @@
43
44
  "menu": "Menu",
44
45
  "rack.placeholder": "Lettres",
45
46
  "rack.tile.location": "Chevalet: la case ({{index}})",
47
+ "rack.touchscreen.placeholder": "Lettres…",
46
48
  "remaining-tiles": "Cases restantes",
47
49
  "results": "Résultats",
48
50
  "results.empty-state.no-results": "Pas de résultats - impossible de générer des mots.",
@@ -58,6 +60,9 @@
58
60
  "settings.autoGroupTiles.right": "Vers la gauche",
59
61
  "settings.autoGroupTiles.null": "Ne pas grouper",
60
62
  "settings.game": "Jeu",
63
+ "settings.inputMode": "Mode de saisie",
64
+ "settings.inputMode.keyboard": "Clavier",
65
+ "settings.inputMode.touchscreen": "Écran tactile",
61
66
  "settings.language": "Langue",
62
67
  "words": "Mots créés",
63
68
  "words.invalid": "Incorrect",
package/src/i18n/pl.json CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
+ "cell.enter-word": "Wprowadź słowo",
2
3
  "cell.filter-cell": "Miejsce docelowe",
3
4
  "cell.set-blank": "Oznacz jako blank",
4
5
  "cell.set-not-blank": "Oznacz jako nie blank",
@@ -22,7 +23,7 @@
22
23
  "dictionary.empty-state.no-results": "Nie udało się znaleźć definicji słowa w słowniku.",
23
24
  "dictionary.empty-state.not-allowed": "To słowo nie jest dopuszczalne w grach.",
24
25
  "dictionary.empty-state.uninitialized": "Tu zostanie wyświetlona definicja słowa.",
25
- "dictionary.input.placeholder": "Szukaj w słowniku...",
26
+ "dictionary.input.placeholder": "Szukaj w słowniku",
26
27
  "dictionary.input.title": "Słowa rozdzielone przecinkiem",
27
28
  "empty-state.error": "Błąd",
28
29
  "empty-state.info": "Info",
@@ -43,6 +44,7 @@
43
44
  "menu": "Menu",
44
45
  "rack.placeholder": "Literki",
45
46
  "rack.tile.location": "Stojak: płytka ({{index}})",
47
+ "rack.touchscreen.placeholder": "Literki…",
46
48
  "remaining-tiles": "Pozostałe płytki",
47
49
  "results": "Wyniki",
48
50
  "results.empty-state.no-results": "Brak wyników - nie można wygenerować żadnego słowa.",
@@ -58,6 +60,9 @@
58
60
  "settings.autoGroupTiles.right": "Po prawej",
59
61
  "settings.autoGroupTiles.null": "Nie grupuj",
60
62
  "settings.game": "Gra",
63
+ "settings.inputMode": "Tryb wpisywania",
64
+ "settings.inputMode.keyboard": "Klawiatura",
65
+ "settings.inputMode.touchscreen": "Ekran dotykowy",
61
66
  "settings.language": "Język",
62
67
  "words": "Utworzone słowa",
63
68
  "words.invalid": "Niepoprawne",
@@ -1,4 +1,5 @@
1
- <!-- https://icons.getbootstrap.com/icons/keyboard-fill/ -->
2
- <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
- <path d="M0 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm13 .25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25zM2.25 8a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 3 8.75v-.5A.25.25 0 0 0 2.75 8h-.5zM4 8.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 5 8.75v-.5A.25.25 0 0 0 4.75 8h-.5a.25.25 0 0 0-.25.25zM6.25 8a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 7 8.75v-.5A.25.25 0 0 0 6.75 8h-.5zM8 8.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 9 8.75v-.5A.25.25 0 0 0 8.75 8h-.5a.25.25 0 0 0-.25.25zM13.25 8a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5zm0 2a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5zm-3-2a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h1.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-1.5zm.75 2.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25zM11.25 6a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5zM9 6.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5A.25.25 0 0 0 9.75 6h-.5a.25.25 0 0 0-.25.25zM7.25 6a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 8 6.75v-.5A.25.25 0 0 0 7.75 6h-.5zM5 6.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 6 6.75v-.5A.25.25 0 0 0 5.75 6h-.5a.25.25 0 0 0-.25.25zM2.25 6a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h1.5A.25.25 0 0 0 4 6.75v-.5A.25.25 0 0 0 3.75 6h-1.5zM2 10.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25zM4.25 10a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h5.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-5.5z" fill="currentColor" />
1
+ <!-- https://icons.getbootstrap.com/icons/keyboard/ -->
2
+ <svg fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M14 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h12zM2 4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2z" />
4
+ <path d="M13 10.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm0-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5 0A.25.25 0 0 1 8.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 8 8.75v-.5zm2 0a.25.25 0 0 1 .25-.25h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5a.25.25 0 0 1-.25-.25v-.5zm1 2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5-2A.25.25 0 0 1 6.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 6 8.75v-.5zm-2 0A.25.25 0 0 1 4.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 4 8.75v-.5zm-2 0A.25.25 0 0 1 2.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 2 8.75v-.5zm11-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0A.25.25 0 0 1 9.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 9 6.75v-.5zm-2 0A.25.25 0 0 1 7.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 7 6.75v-.5zm-2 0A.25.25 0 0 1 5.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 5 6.75v-.5zm-3 0A.25.25 0 0 1 2.25 6h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5A.25.25 0 0 1 2 6.75v-.5zm0 4a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm2 0a.25.25 0 0 1 .25-.25h5.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-5.5a.25.25 0 0 1-.25-.25v-.5z" />
4
5
  </svg>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/keyboard-fill/ -->
2
+ <svg fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M0 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm13 .25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25zM2.25 8a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 3 8.75v-.5A.25.25 0 0 0 2.75 8h-.5zM4 8.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 5 8.75v-.5A.25.25 0 0 0 4.75 8h-.5a.25.25 0 0 0-.25.25zM6.25 8a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 7 8.75v-.5A.25.25 0 0 0 6.75 8h-.5zM8 8.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 9 8.75v-.5A.25.25 0 0 0 8.75 8h-.5a.25.25 0 0 0-.25.25zM13.25 8a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5zm0 2a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5zm-3-2a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h1.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-1.5zm.75 2.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25zM11.25 6a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5zM9 6.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5A.25.25 0 0 0 9.75 6h-.5a.25.25 0 0 0-.25.25zM7.25 6a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 8 6.75v-.5A.25.25 0 0 0 7.75 6h-.5zM5 6.25v.5c0 .138.112.25.25.25h.5A.25.25 0 0 0 6 6.75v-.5A.25.25 0 0 0 5.75 6h-.5a.25.25 0 0 0-.25.25zM2.25 6a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h1.5A.25.25 0 0 0 4 6.75v-.5A.25.25 0 0 0 3.75 6h-1.5zM2 10.25v.5c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25zM4.25 10a.25.25 0 0 0-.25.25v.5c0 .138.112.25.25.25h5.5a.25.25 0 0 0 .25-.25v-.5a.25.25 0 0 0-.25-.25h-5.5z" />
4
+ </svg>
@@ -31,6 +31,7 @@ export { default as FlagUs } from './FlagUs.svg';
31
31
  export { default as Github } from './Github.svg';
32
32
  export { default as InfoCircleFill } from './InfoCircleFill.svg';
33
33
  export { default as Keyboard } from './Keyboard.svg';
34
+ export { default as KeyboardFill } from './KeyboardFill.svg';
34
35
  export { default as List } from './List.svg';
35
36
  export { default as Sack } from './Sack.svg';
36
37
  export { default as Search } from './Search.svg';
@@ -0,0 +1,26 @@
1
+ import { scrabble } from '@scrabble-solver/configs';
2
+ import { BLANK } from '@scrabble-solver/constants';
3
+ import { Locale } from '@scrabble-solver/types';
4
+
5
+ import extractCharacters from './extractCharacters';
6
+
7
+ const tests = [
8
+ { input: 'ab ', expected: ['a', 'b', BLANK] },
9
+ { input: 'śćźa', expected: ['a'] },
10
+ { input: 'bueno', expected: ['b', 'u', 'e', 'n', 'o'] },
11
+ { input: 'bellas', expected: ['b', 'e', 'll', 'a', 's'] },
12
+ { input: 'BELLAS', expected: ['b', 'e', 'll', 'a', 's'] },
13
+ { input: 'churro', expected: ['ch', 'u', 'rr', 'o'] },
14
+ { input: 'challulla', expected: ['ch', 'a', 'll', 'u', 'll', 'a'] },
15
+ ];
16
+
17
+ describe('extractCharacters', () => {
18
+ const locale = Locale.ES_ES;
19
+ const config = scrabble[locale];
20
+
21
+ for (const { input, expected } of tests) {
22
+ it(`[${locale}] "${input}"`, () => {
23
+ expect(extractCharacters(config, input)).toEqual(expected);
24
+ });
25
+ }
26
+ });
@@ -4,20 +4,22 @@ import { Config } from '@scrabble-solver/types';
4
4
  const extractCharacters = (config: Config, value: string): string[] => {
5
5
  let index = 0;
6
6
  const characters: string[] = [];
7
+ const valueLowercase = value.toLocaleLowerCase(config.locale);
7
8
 
8
- while (index < value.length) {
9
- const character = value[index];
10
- const nextCharacter = value[index + 1];
11
- const twoCharacterTileCandidate = `${character}${nextCharacter}`;
9
+ while (index < valueLowercase.length) {
10
+ const character = valueLowercase[index];
11
+ const nextCharacter = valueLowercase[index + 1];
12
+ const digraph = `${character}${nextCharacter}`;
12
13
 
13
- if (config.twoCharacterTiles.includes(twoCharacterTileCandidate)) {
14
- characters.push(twoCharacterTileCandidate);
15
- ++index;
14
+ if (config.twoCharacterTiles.includes(digraph)) {
15
+ characters.push(digraph);
16
+ index += digraph.length;
16
17
  } else if (config.hasCharacter(character) || character === BLANK) {
17
18
  characters.push(character);
19
+ ++index;
20
+ } else {
21
+ ++index;
18
22
  }
19
-
20
- ++index;
21
23
  }
22
24
 
23
25
  return characters;
@@ -0,0 +1,31 @@
1
+ import { scrabble } from '@scrabble-solver/configs';
2
+ import { BLANK } from '@scrabble-solver/constants';
3
+ import { Locale } from '@scrabble-solver/types';
4
+
5
+ import extractCharactersByCase from './extractCharactersByCase';
6
+
7
+ const tests = [
8
+ { input: 'ab ', expected: ['a', 'b', BLANK] },
9
+ { input: 'śćźa', expected: ['a'] },
10
+ { input: 'bueno', expected: ['b', 'u', 'e', 'n', 'o'] },
11
+ { input: 'bellas', expected: ['b', 'e', 'l', 'l', 'a', 's'] },
12
+ { input: 'churro', expected: ['c', 'h', 'u', 'r', 'r', 'o'] },
13
+ { input: 'challulla', expected: ['c', 'h', 'a', 'l', 'l', 'u', 'l', 'l', 'a'] },
14
+ { input: 'beLlas', expected: ['b', 'e', 'l', 'l', 'a', 's'] },
15
+ { input: 'belLas', expected: ['b', 'e', 'l', 'l', 'a', 's'] },
16
+ { input: 'beLLas', expected: ['b', 'e', 'll', 'a', 's'] },
17
+ { input: 'chuRRo', expected: ['c', 'h', 'u', 'rr', 'o'] },
18
+ { input: 'CHuRRo', expected: ['ch', 'u', 'rr', 'o'] },
19
+ { input: 'CHaLLuLLa', expected: ['ch', 'a', 'll', 'u', 'll', 'a'] },
20
+ ];
21
+
22
+ describe('extractCharactersByCase', () => {
23
+ const locale = Locale.ES_ES;
24
+ const config = scrabble[locale];
25
+
26
+ for (const { input, expected } of tests) {
27
+ it(`[${locale}] "${input}"`, () => {
28
+ expect(extractCharactersByCase(config, input)).toEqual(expected);
29
+ });
30
+ }
31
+ });
@@ -0,0 +1,31 @@
1
+ import { BLANK } from '@scrabble-solver/constants';
2
+ import { Config } from '@scrabble-solver/types';
3
+
4
+ import isUpperCase from './isUpperCase';
5
+
6
+ const extractCharactersByCase = (config: Config, value: string): string[] => {
7
+ let index = 0;
8
+ const characters: string[] = [];
9
+
10
+ while (index < value.length) {
11
+ const character = value[index];
12
+ const characterLowercase = value[index].toLocaleLowerCase(config.locale);
13
+ const nextCharacter = value[index + 1];
14
+ const digraph = `${character}${nextCharacter}`;
15
+ const digraphLowercase = digraph.toLocaleLowerCase(config.locale);
16
+
17
+ if (isUpperCase(config.locale, digraph) && config.twoCharacterTiles.includes(digraphLowercase)) {
18
+ characters.push(digraphLowercase);
19
+ index += digraphLowercase.length;
20
+ } else if (config.hasCharacter(characterLowercase) || characterLowercase === BLANK) {
21
+ characters.push(characterLowercase);
22
+ ++index;
23
+ } else {
24
+ ++index;
25
+ }
26
+ }
27
+
28
+ return characters;
29
+ };
30
+
31
+ export default extractCharactersByCase;
package/src/lib/index.ts CHANGED
@@ -3,14 +3,15 @@ export { default as canUseDom } from './canUseDom';
3
3
  export { default as createArray } from './createArray';
4
4
  export { default as createComparator } from './createComparator';
5
5
  export { default as createGridOf } from './createGridOf';
6
- export { default as createKeyboardNavigation } from './createKeyboardNavigation';
7
6
  export { default as createKeyComparator } from './createKeyComparator';
7
+ export { default as createKeyboardNavigation } from './createKeyboardNavigation';
8
8
  export { default as createNullMovingComparator } from './createNullMovingComparator';
9
9
  export { default as createRegExp } from './createRegExp';
10
10
  export { default as createStringComparator } from './createStringComparator';
11
11
  export { default as dataUrlToBlob } from './dataUrlToBlob';
12
12
  export { default as detectLocale } from './detectLocale';
13
13
  export { default as extractCharacters } from './extractCharacters';
14
+ export { default as extractCharactersByCase } from './extractCharactersByCase';
14
15
  export { default as extractInputValue } from './extractInputValue';
15
16
  export { default as findCell } from './findCell';
16
17
  export { default as getCellSize } from './getCellSize';
@@ -26,6 +27,7 @@ export { default as isCtrl } from './isCtrl';
26
27
  export { default as isMac } from './isMac';
27
28
  export { default as isRegExp } from './isRegExp';
28
29
  export { default as isStringArray } from './isStringArray';
30
+ export { default as isUpperCase } from './isUpperCase';
29
31
  export { default as memoize } from './memoize';
30
32
  export { default as noop } from './noop';
31
33
  export { default as numberComparator } from './numberComparator';
@@ -0,0 +1,7 @@
1
+ import { Locale } from '@scrabble-solver/types';
2
+
3
+ const isUpperCase = (locale: Locale, value: string): boolean => {
4
+ return value === value.toLocaleUpperCase(locale);
5
+ };
6
+
7
+ export default isUpperCase;
@@ -3,7 +3,7 @@ import { FunctionComponent, memo } from 'react';
3
3
  import { Modal } from 'components';
4
4
  import { useTranslate } from 'state';
5
5
 
6
- import { AutoGroupTilesSetting, ConfigSetting, LocaleSetting } from './components';
6
+ import { AutoGroupTilesSetting, ConfigSetting, InputModeSetting, LocaleSetting } from './components';
7
7
 
8
8
  interface Props {
9
9
  className?: string;
@@ -24,6 +24,10 @@ const SettingsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
24
24
  <LocaleSetting disabled={!isOpen} />
25
25
  </Modal.Section>
26
26
 
27
+ <Modal.Section title={translate('settings.inputMode')}>
28
+ <InputModeSetting disabled={!isOpen} />
29
+ </Modal.Section>
30
+
27
31
  <Modal.Section title={translate('settings.autoGroupTiles')}>
28
32
  <AutoGroupTilesSetting disabled={!isOpen} />
29
33
  </Modal.Section>
@@ -0,0 +1,12 @@
1
+ .option {
2
+ margin-bottom: var(--spacing--m);
3
+
4
+ &:last-child {
5
+ margin-bottom: 0;
6
+ }
7
+ }
8
+
9
+ .label {
10
+ display: flex;
11
+ align-items: center;
12
+ }
@@ -0,0 +1,55 @@
1
+ import { ChangeEvent, FunctionComponent } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+
4
+ import { Radio } from 'components';
5
+ import { selectInputMode, settingsSlice, useTranslate, useTypedSelector } from 'state';
6
+
7
+ import styles from './InputModeSetting.module.scss';
8
+ import { parseValue } from './lib';
9
+
10
+ interface Props {
11
+ className?: string;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ const InputModeSetting: FunctionComponent<Props> = ({ className, disabled }) => {
16
+ const dispatch = useDispatch();
17
+ const translate = useTranslate();
18
+ const value = useTypedSelector(selectInputMode);
19
+
20
+ const options = [
21
+ {
22
+ label: translate('settings.inputMode.keyboard'),
23
+ value: 'keyboard',
24
+ },
25
+ {
26
+ label: translate('settings.inputMode.touchscreen'),
27
+ value: 'touchscreen',
28
+ },
29
+ ];
30
+
31
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
32
+ const inputMode = parseValue(event.target.value);
33
+ dispatch(settingsSlice.actions.changeInputMode(inputMode));
34
+ };
35
+
36
+ return (
37
+ <div className={className}>
38
+ {options.map((option) => (
39
+ <Radio
40
+ checked={value === option.value}
41
+ className={styles.option}
42
+ disabled={disabled}
43
+ key={option.value}
44
+ name="inputMode"
45
+ value={option.value}
46
+ onChange={handleChange}
47
+ >
48
+ <div className={styles.label}>{option.label}</div>
49
+ </Radio>
50
+ ))}
51
+ </div>
52
+ );
53
+ };
54
+
55
+ export default InputModeSetting;
@@ -0,0 +1 @@
1
+ export { default } from './InputModeSetting';
@@ -0,0 +1,13 @@
1
+ import { InputMode } from 'types';
2
+
3
+ export const parseValue = (value: string): InputMode => {
4
+ if (value === 'keyboard') {
5
+ return 'keyboard';
6
+ }
7
+
8
+ if (value === 'touchscreen') {
9
+ return 'touchscreen';
10
+ }
11
+
12
+ throw new Error(`"${value}" is not valid. Should be "keyboard" or "touchscreen"`);
13
+ };
@@ -0,0 +1,7 @@
1
+ import { InputMode } from 'types';
2
+
3
+ export interface Option {
4
+ className: string;
5
+ label: string;
6
+ value: InputMode;
7
+ }
@@ -1,3 +1,4 @@
1
1
  export { default as AutoGroupTilesSetting } from './AutoGroupTilesSetting';
2
2
  export { default as ConfigSetting } from './ConfigSetting';
3
+ export { default as InputModeSetting } from './InputModeSetting';
3
4
  export { default as LocaleSetting } from './LocaleSetting';
@@ -1,11 +1,12 @@
1
1
  import { Board, Locale } from '@scrabble-solver/types';
2
2
  import store2 from 'store2';
3
3
 
4
- import { AutoGroupTiles, Rack } from 'types';
4
+ import { AutoGroupTiles, InputMode, Rack } from 'types';
5
5
 
6
6
  const AUTO_GROUP_TILES = 'auto-group-tiles';
7
7
  const BOARD = 'board';
8
8
  const CONFIG_ID = 'config-id';
9
+ const INPUT_MODE = 'input-mode';
9
10
  const LOCALE = 'locale';
10
11
  const RACK = 'rack';
11
12
 
@@ -38,6 +39,14 @@ const localStorage = {
38
39
  store.set(CONFIG_ID, configId, true);
39
40
  },
40
41
 
42
+ getInputMode(): InputMode | undefined {
43
+ return store.get(INPUT_MODE);
44
+ },
45
+
46
+ setInputMode(inputMode: InputMode | undefined): void {
47
+ store.set(INPUT_MODE, inputMode, true);
48
+ },
49
+
41
50
  getLocale(): Locale | undefined {
42
51
  return store.get(LOCALE);
43
52
  },
@@ -65,6 +65,8 @@ export const selectLocaleAutoGroupTiles = createSelector([selectLocale, selectSe
65
65
 
66
66
  export const selectBoard = selectBoardRoot;
67
67
 
68
+ export const selectInputMode = createSelector([selectSettingsRoot], (settings) => settings.inputMode);
69
+
68
70
  export const selectConfigId = createSelector([selectSettingsRoot], (settings) => settings.configId);
69
71
 
70
72
  export const selectConfig = createSelector([selectConfigId, selectLocale], getLocaleConfig);
@@ -2,21 +2,24 @@ import { literaki, scrabble } from '@scrabble-solver/configs';
2
2
  import { Locale } from '@scrabble-solver/types';
3
3
 
4
4
  import { guessLocale } from 'lib';
5
- import { AutoGroupTiles } from 'types';
5
+ import { AutoGroupTiles, InputMode } from 'types';
6
6
 
7
7
  import localStorage from '../localStorage';
8
8
 
9
9
  export interface SettingsState {
10
10
  autoGroupTiles: AutoGroupTiles;
11
11
  configId: typeof literaki.id | typeof scrabble.id;
12
+ inputMode: InputMode;
12
13
  locale: Locale;
13
14
  }
14
15
 
15
16
  const localStorageAutoGroupTiles = localStorage.getAutoGroupTiles();
17
+ const isTouchScreen = typeof globalThis.matchMedia !== 'undefined' && globalThis.matchMedia('(hover: none)').matches;
16
18
 
17
19
  const settingsInitialState: SettingsState = {
18
20
  autoGroupTiles: typeof localStorageAutoGroupTiles === 'undefined' ? 'left' : localStorageAutoGroupTiles,
19
21
  configId: localStorage.getConfigId() || scrabble.id,
22
+ inputMode: localStorage.getInputMode() || (isTouchScreen ? 'touchscreen' : 'keyboard'),
20
23
  locale: localStorage.getLocale() || guessLocale(),
21
24
  };
22
25
 
@@ -1,7 +1,7 @@
1
1
  import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
2
  import { Locale } from '@scrabble-solver/types';
3
3
 
4
- import { AutoGroupTiles } from 'types';
4
+ import { AutoGroupTiles, InputMode } from 'types';
5
5
 
6
6
  import settingsInitialState from './settingsInitialState';
7
7
 
@@ -19,6 +19,11 @@ const settingsSlice = createSlice({
19
19
  return { ...state, configId };
20
20
  },
21
21
 
22
+ changeInputMode: (state, action: PayloadAction<InputMode>) => {
23
+ const inputMode = action.payload;
24
+ return { ...state, inputMode };
25
+ },
26
+
22
27
  changeLocale: (state, action: PayloadAction<Locale>) => {
23
28
  const locale = action.payload;
24
29
  return { ...state, locale };
@@ -103,6 +103,7 @@ $media-expressions: (
103
103
  padding: 0 var(--spacing--l);
104
104
  transition: var(--transition);
105
105
  font-size: var(--font--size--m);
106
+ outline: none;
106
107
  }
107
108
 
108
109
  @mixin text-stroke($color, $size: 1px) {
@@ -39,6 +39,7 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
39
39
  --color--background--overlay: rgba(255, 255, 255, 0.65);
40
40
  --color--foreground: #222;
41
41
  --color--foreground--secondary: #444;
42
+ --color--mark: #ffff00;
42
43
  --color--error: hsl(0, 92%, 62%);
43
44
  --color--error--opposite: var(--color--white);
44
45
  --color--info: #1868ad;
@@ -4,6 +4,8 @@ export type AutoGroupTiles = 'left' | 'right' | null;
4
4
 
5
5
  export type Direction = 'horizontal' | 'vertical';
6
6
 
7
+ export type InputMode = 'keyboard' | 'touchscreen';
8
+
7
9
  export interface Point {
8
10
  x: number;
9
11
  y: number;
@@ -45,6 +47,7 @@ export enum ResultColumn {
45
47
  WordsCount = 'words-count',
46
48
  }
47
49
  export type TranslationKey =
50
+ | 'cell.enter-word'
48
51
  | 'cell.filter-cell'
49
52
  | 'cell.set-blank'
50
53
  | 'cell.set-not-blank'
@@ -89,6 +92,7 @@ export type TranslationKey =
89
92
  | 'menu'
90
93
  | 'rack.placeholder'
91
94
  | 'rack.tile.location'
95
+ | 'rack.touchscreen.placeholder'
92
96
  | 'remaining-tiles'
93
97
  | 'results'
94
98
  | 'results.empty-state.no-results'
@@ -104,6 +108,9 @@ export type TranslationKey =
104
108
  | 'settings.autoGroupTiles.right'
105
109
  | 'settings.autoGroupTiles.null'
106
110
  | 'settings.game'
111
+ | 'settings.inputMode'
112
+ | 'settings.inputMode.keyboard'
113
+ | 'settings.inputMode.touchscreen'
107
114
  | 'settings.language'
108
115
  | 'words'
109
116
  | 'words.invalid'
@@ -1 +0,0 @@
1
- self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/css/c6e0e01f44fc0425.css","static/chunks/pages/index-6894f40e6cac9243.js"],"/404":["static/chunks/pages/404-ca203fa27afc37d8.js"],"/_error":["static/chunks/pages/_error-54de1933a164a1ff.js"],sortedPages:["/","/404","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();