@maestro_io/maestro-web-sdk 3.3.1 → 4.0.0-beta.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 (269) hide show
  1. package/dist/f8820fca3a2a7957f753167da0884f9d.png +0 -0
  2. package/dist/maestro-web-sdk.esm.js +12 -12
  3. package/dist/maestro-web-sdk.esm.js.map +1 -1
  4. package/dist/maestro-web-sdk.umd.js +12 -12
  5. package/dist/maestro-web-sdk.umd.js.map +1 -1
  6. package/dist/package.json +87 -0
  7. package/dist/src/__mocks__/sentry.d.ts +4 -0
  8. package/dist/src/components/atoms/BaseButton/BaseButton.js +11 -0
  9. package/dist/src/components/atoms/BaseButton/index.js +2 -0
  10. package/dist/src/components/atoms/Image/index.js +34 -0
  11. package/dist/src/components/atoms/Rive/index.js +53 -0
  12. package/dist/src/components/atoms/SvgIcon/BetGeneralIcon.js +9 -0
  13. package/dist/src/components/atoms/SvgIcon/BetsIcon.js +11 -0
  14. package/dist/src/components/atoms/SvgIcon/BetsWarningIcon.js +11 -0
  15. package/dist/src/components/atoms/SvgIcon/CheckmarkIcon.js +6 -0
  16. package/dist/src/components/atoms/SvgIcon/CollapseIcon.js +6 -0
  17. package/dist/src/components/atoms/SvgIcon/ExpandIcon.js +6 -0
  18. package/dist/src/components/atoms/SvgIcon/FantasyIcon.js +15 -0
  19. package/dist/src/components/atoms/SvgIcon/FlameIcon.js +11 -0
  20. package/dist/src/components/atoms/SvgIcon/Icon.js +44 -0
  21. package/dist/src/components/atoms/SvgIcon/KeyPlaysIcon.js +10 -0
  22. package/dist/src/components/atoms/SvgIcon/MobilePhoneIcon.js +11 -0
  23. package/dist/src/components/atoms/SvgIcon/PlayIcon.js +8 -0
  24. package/dist/src/components/atoms/SvgIcon/ShopIcon.js +7 -0
  25. package/dist/src/components/atoms/SvgIcon/StatsIcon.js +12 -0
  26. package/dist/src/components/atoms/SvgIcon/TimesIcon.js +6 -0
  27. package/dist/src/components/atoms/SvgIcon/WarningIcon.js +8 -0
  28. package/dist/src/components/atoms/SvgIcon/index.js +2 -0
  29. package/dist/src/components/core/App/App.js +35 -0
  30. package/dist/src/components/core/App/index.js +2 -0
  31. package/dist/src/components/core/OverlayContainer.js +58 -0
  32. package/dist/src/components/core/PanelManager/PanelManager.js +86 -0
  33. package/dist/src/components/core/PanelManager/index.js +2 -0
  34. package/dist/src/components/core/ScrollableContainer/FocusableItem.js +38 -0
  35. package/dist/src/components/core/ScrollableContainer/ScrollableContainer.js +168 -0
  36. package/dist/src/components/core/ScrollableContainer/index.js +2 -0
  37. package/dist/src/components/molecules/ActionButton/ActionButton.js +10 -0
  38. package/dist/src/components/molecules/ActionButton/index.js +2 -0
  39. package/dist/src/components/molecules/HeadshotImage/HeadshotImage.js +50 -0
  40. package/dist/src/components/molecules/HeadshotImage/index.js +2 -0
  41. package/dist/src/components/molecules/Overlay/Overlay.js +139 -0
  42. package/dist/src/components/molecules/Overlay/index.js +2 -0
  43. package/dist/src/components/molecules/PanelNavButton/PanelNavButton.js +27 -0
  44. package/dist/src/components/molecules/PanelNavButton/index.js +2 -0
  45. package/dist/src/components/molecules/SegmentButton/SegmentButton.js +10 -0
  46. package/dist/src/components/molecules/SegmentButton/index.js +2 -0
  47. package/dist/src/components/molecules/TabLoadError/TabLoadError.js +14 -0
  48. package/dist/src/components/molecules/TabLoadError/index.js +2 -0
  49. package/dist/src/components/molecules/WatchButton/WatchButton.js +15 -0
  50. package/dist/src/components/molecules/WatchButton/index.js +2 -0
  51. package/dist/src/components/organisms/PanelNavigation/PanelNavigation.js +60 -0
  52. package/dist/src/components/organisms/PanelNavigation/index.js +2 -0
  53. package/dist/src/components/organisms/SegmentController/SegmentController.js +76 -0
  54. package/dist/src/components/organisms/SegmentController/index.js +2 -0
  55. package/dist/src/development/MaestroEventDelegate.js +109 -0
  56. package/dist/src/development/TimeSeek.js +218 -0
  57. package/dist/src/development/development.js +33 -0
  58. package/dist/src/external/AxiosNetworkClient.js +110 -0
  59. package/dist/src/external/LocalStorageCacheManager.js +213 -0
  60. package/dist/src/external/ReactRenderer.js +53 -0
  61. package/dist/src/external/mocks/MockExternalNetworkClient.js +187 -0
  62. package/dist/src/external/ports/cacheManager.js +1 -0
  63. package/dist/src/external/ports/networkClient.js +1 -0
  64. package/dist/src/external/ports/renderer.d.ts +1 -2
  65. package/dist/src/external/ports/renderer.js +1 -0
  66. package/dist/src/external/spatial-navigation/__mocks__/withFocusable.d.ts +8 -0
  67. package/dist/src/external/spatial-navigation/__mocks__/withFocusable.js +7 -0
  68. package/dist/src/external/spatial-navigation/index.js +3 -0
  69. package/dist/src/external/spatial-navigation/measureLayout.js +30 -0
  70. package/dist/src/external/spatial-navigation/spatialNavigation.js +876 -0
  71. package/dist/src/external/spatial-navigation/utils.js +6 -0
  72. package/dist/src/external/spatial-navigation/visualDebugger.js +76 -0
  73. package/dist/src/external/spatial-navigation/withFocusable.js +245 -0
  74. package/dist/src/helpers/Observable.js +37 -0
  75. package/dist/src/helpers/strings.js +14 -0
  76. package/dist/src/helpers/url.js +12 -0
  77. package/dist/src/index.d.ts +5 -5
  78. package/dist/src/index.js +165 -0
  79. package/dist/src/interfaces/IMaestroEvent.d.ts +12 -0
  80. package/dist/src/interfaces/IMaestroEvent.js +18 -0
  81. package/dist/src/interfaces/IMaestroEventDelegate.js +1 -0
  82. package/dist/src/interfaces/IMaestroManager.js +1 -0
  83. package/dist/src/interfaces/IUserSettings.js +20 -0
  84. package/dist/src/models/IModel.js +1 -0
  85. package/dist/src/models/IPanel.js +40 -0
  86. package/dist/src/models/ITheme.js +20 -0
  87. package/dist/src/models/IWinningBet.js +1 -0
  88. package/dist/src/modules/bets/interfaces/IBets.js +1 -0
  89. package/dist/src/modules/bets/mocks.js +573 -0
  90. package/dist/src/modules/bets/types/BetsAnalyticsTypes.js +1 -0
  91. package/dist/src/modules/bets/view/BetsView.js +143 -0
  92. package/dist/src/modules/bets/view/HotProps/HotPropsView.js +150 -0
  93. package/dist/src/modules/bets/view/SixPack/SixPackView.js +110 -0
  94. package/dist/src/modules/bets/view/UserBets/BetResult/BetResult.js +6 -0
  95. package/dist/src/modules/bets/view/UserBets/BetsCard/BetsCard.js +149 -0
  96. package/dist/src/modules/bets/view/UserBets/BetsCard/BetsCardEvent.js +34 -0
  97. package/dist/src/modules/bets/view/UserBets/BetsCard/BetsCardFooter.js +18 -0
  98. package/dist/src/modules/bets/view/UserBets/Boost/Boost.js +6 -0
  99. package/dist/src/modules/bets/view/UserBets/OutcomeBadge/OutcomeBadge.js +9 -0
  100. package/dist/src/modules/bets/view/UserBets/Parlay/Parlay.js +13 -0
  101. package/dist/src/modules/bets/view/UserBets/Straight/Straight.js +6 -0
  102. package/dist/src/modules/bets/view/UserBets/Teaser/Teaser.js +13 -0
  103. package/dist/src/modules/bets/view/UserBets/UserBetsView.js +107 -0
  104. package/dist/src/modules/bets/view/components/Bet/Bet.js +32 -0
  105. package/dist/src/modules/bets/view/components/BetPill/BetPill.js +6 -0
  106. package/dist/src/modules/bets/view/components/BetsCard/BetsCard.js +15 -0
  107. package/dist/src/modules/bets/view/components/BetsCard/BetsCardHeader.js +20 -0
  108. package/dist/src/modules/bets/view/components/GamblingDisclaimer/GamblingDisclaimer.js +14 -0
  109. package/dist/src/modules/bets/view/components/LocationWarning/LocationWarning.js +16 -0
  110. package/dist/src/modules/bets/view/components/PromoCodeBanner/PromoCodeBanner.js +78 -0
  111. package/dist/src/modules/bets/view/components/PromoCodeBanner/index.js +1 -0
  112. package/dist/src/modules/bets/view-model/BetsViewModel.js +974 -0
  113. package/dist/src/modules/fantasy/components/ExpandedAthleteList/ExpandedAthletesList.js +107 -0
  114. package/dist/src/modules/fantasy/components/ExpandedAthleteList/index.js +1 -0
  115. package/dist/src/modules/fantasy/components/LeagueCard/LeagueCard.js +20 -0
  116. package/dist/src/modules/fantasy/components/LeagueCard/index.js +2 -0
  117. package/dist/src/modules/fantasy/components/TopPlayerCard/TopPlayerCard.js +29 -0
  118. package/dist/src/modules/fantasy/components/TopPlayerCard/index.js +2 -0
  119. package/dist/src/modules/fantasy/mocks.js +3682 -0
  120. package/dist/src/modules/fantasy/types.js +6 -0
  121. package/dist/src/modules/fantasy/view/ActiveAthletesMarquee/ActiveAthletesMarquee.js +74 -0
  122. package/dist/src/modules/fantasy/view/ActiveAthletesMarquee/index.js +2 -0
  123. package/dist/src/modules/fantasy/view/ErrorBoundary.d.ts +13 -0
  124. package/dist/src/modules/fantasy/view/FantasyItemView.js +74 -0
  125. package/dist/src/modules/fantasy/view/FantasyView.js +133 -0
  126. package/dist/src/modules/fantasy/view/InactiveLeague/DraftDateCountdown.js +95 -0
  127. package/dist/src/modules/fantasy/view/InactiveLeague/InactiveLeague.js +13 -0
  128. package/dist/src/modules/fantasy/view/InactiveLeague/index.js +2 -0
  129. package/dist/src/modules/fantasy/view/LoginView/LoginView.js +27 -0
  130. package/dist/src/modules/fantasy/view/LoginView/index.js +2 -0
  131. package/dist/src/modules/fantasy/view/MatchupPair/DoubleAthleteCard/DoubleAthleteCard.js +25 -0
  132. package/dist/src/modules/fantasy/view/MatchupPair/DoubleAthleteCard/index.js +2 -0
  133. package/dist/src/modules/fantasy/view/MatchupPair/ExpandedDoubleAthleteCards.js +9 -0
  134. package/dist/src/modules/fantasy/view/MatchupPair/MatchupPair.js +118 -0
  135. package/dist/src/modules/fantasy/view/MatchupPair/index.js +2 -0
  136. package/dist/src/modules/fantasy/view/MatchupSingle/ExpandedSingleAthleteCards.js +9 -0
  137. package/dist/src/modules/fantasy/view/MatchupSingle/MatchupSingle.js +108 -0
  138. package/dist/src/modules/fantasy/view/MatchupSingle/SingleAthleteCard/SingleAthleteCard.js +18 -0
  139. package/dist/src/modules/fantasy/view/MatchupSingle/SingleAthleteCard/index.js +2 -0
  140. package/dist/src/modules/fantasy/view/MatchupSingle/index.js +2 -0
  141. package/dist/src/modules/fantasy/view/PromoView/PromoView.js +12 -0
  142. package/dist/src/modules/fantasy/view/PromoView/index.js +2 -0
  143. package/dist/src/modules/fantasy/view/index.js +2 -0
  144. package/dist/src/modules/fantasy/view-model/FantasyViewModel.FantasyPolling.js +132 -0
  145. package/dist/src/modules/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.js +141 -0
  146. package/dist/src/modules/fantasy/view-model/FantasyViewModel.js +534 -0
  147. package/dist/src/modules/key-plays/constants.js +292 -0
  148. package/dist/src/modules/key-plays/interfaces/IKeyPlays.js +14 -0
  149. package/dist/src/modules/key-plays/interfaces/IKeyPlaysResponse.js +1 -0
  150. package/dist/src/modules/key-plays/view/KeyPlayCardScoreTagView.js +16 -0
  151. package/dist/src/modules/key-plays/view/KeyPlayCardView.js +144 -0
  152. package/dist/src/modules/key-plays/view/KeyPlayErrorStateView.js +65 -0
  153. package/dist/src/modules/key-plays/view/KeyPlaysView.js +151 -0
  154. package/dist/src/modules/key-plays/view-model/KeyPlaysViewModel.js +246 -0
  155. package/dist/src/modules/shop/mocks.js +390 -0
  156. package/dist/src/modules/shop/types.js +7 -0
  157. package/dist/src/modules/shop/view/ProductDetailView/ProductDetailView.js +56 -0
  158. package/dist/src/modules/shop/view/ProductDetailView/index.js +2 -0
  159. package/dist/src/modules/shop/view/ShopView.js +182 -0
  160. package/dist/src/modules/shop/view/components/BrowseCard/BrowseCard.js +23 -0
  161. package/dist/src/modules/shop/view/components/BrowseCard/index.js +2 -0
  162. package/dist/src/modules/shop/view/components/ProductSummaryCard/ProductSummaryCard.js +26 -0
  163. package/dist/src/modules/shop/view/components/ProductSummaryCard/index.js +1 -0
  164. package/dist/src/modules/shop/view/components/ShopPromoView/ShopPromoView.js +19 -0
  165. package/dist/src/modules/shop/view/components/ShopPromoView/index.js +1 -0
  166. package/dist/src/modules/shop/view/components/ShopSection/ShopSection.d.ts +1 -0
  167. package/dist/src/modules/shop/view/components/ShopSection/ShopSection.js +61 -0
  168. package/dist/src/modules/shop/view/components/ShopSection/ShopSectionItem.d.ts +18 -0
  169. package/dist/src/modules/shop/view/components/ShopSection/ShopSectionItem.js +22 -0
  170. package/dist/src/modules/shop/view/components/ShopSection/index.js +1 -0
  171. package/dist/src/modules/shop/view/index.js +2 -0
  172. package/dist/src/modules/shop/view-model/ShopViewModel.ShopPolling.js +111 -0
  173. package/dist/src/modules/shop/view-model/ShopViewModel.js +353 -0
  174. package/dist/src/modules/stats/interfaces/IStats.js +1 -0
  175. package/dist/src/modules/stats/mocks.js +746 -0
  176. package/dist/src/modules/stats/utils/driveSummaryUtils.js +48 -0
  177. package/dist/src/modules/stats/utils/renderTeamStatistic.js +16 -0
  178. package/dist/src/modules/stats/utils.js +62 -0
  179. package/dist/src/modules/stats/view/ActiveAthletes/ActiveAthletes.js +98 -0
  180. package/dist/src/modules/stats/view/ActiveAthletes/CollapsedAthletes/CollapsedAthletes.js +27 -0
  181. package/dist/src/modules/stats/view/ActiveAthletes/ExpandedAthletes/ExpandedAthletes.js +89 -0
  182. package/dist/src/modules/stats/view/ActiveAthletes/index.js +2 -0
  183. package/dist/src/modules/stats/view/DriveSummary/DrivePlayCard.js +70 -0
  184. package/dist/src/modules/stats/view/DriveSummary/DriveSummary.js +87 -0
  185. package/dist/src/modules/stats/view/DriveSummary/ExpandedItems/DriveSummaryExpanded.js +129 -0
  186. package/dist/src/modules/stats/view/DriveSummary/Header/DriveSummaryHeader.js +26 -0
  187. package/dist/src/modules/stats/view/DriveSummary/MainStat/MainStat.js +24 -0
  188. package/dist/src/modules/stats/view/DriveSummary/index.js +2 -0
  189. package/dist/src/modules/stats/view/GameLeaders/GameLeaders.js +48 -0
  190. package/dist/src/modules/stats/view/GameLeaders/index.js +2 -0
  191. package/dist/src/modules/stats/view/MatchPredictor/DonutChart/DonutChart.js +55 -0
  192. package/dist/src/modules/stats/view/MatchPredictor/DonutChart/index.js +1 -0
  193. package/dist/src/modules/stats/view/MatchPredictor/MatchPredictorView.js +60 -0
  194. package/dist/src/modules/stats/view/MatchPredictor/index.js +2 -0
  195. package/dist/src/modules/stats/view/Penalties/Penalties.js +6 -0
  196. package/dist/src/modules/stats/view/Penalties/index.js +2 -0
  197. package/dist/src/modules/stats/view/RecentGames/RecentGames.js +23 -0
  198. package/dist/src/modules/stats/view/RecentGames/index.js +2 -0
  199. package/dist/src/modules/stats/view/ScoringSummary/ExpandedScores/ExpandedScores.js +121 -0
  200. package/dist/src/modules/stats/view/ScoringSummary/ExpandedScores/index.js +2 -0
  201. package/dist/src/modules/stats/view/ScoringSummary/ScoreItem/ScoreItem.js +62 -0
  202. package/dist/src/modules/stats/view/ScoringSummary/ScoreItem/index.js +2 -0
  203. package/dist/src/modules/stats/view/ScoringSummary/ScoresSection/ScoresSection.js +23 -0
  204. package/dist/src/modules/stats/view/ScoringSummary/ScoresSection/index.js +2 -0
  205. package/dist/src/modules/stats/view/ScoringSummary/ScoringSummary.js +106 -0
  206. package/dist/src/modules/stats/view/ScoringSummary/index.js +2 -0
  207. package/dist/src/modules/stats/view/StatsItemView.js +92 -0
  208. package/dist/src/modules/stats/view/StatsQRCode/StatsQRCodeView.js +36 -0
  209. package/dist/src/modules/stats/view/StatsQRCode/index.js +1 -0
  210. package/dist/src/modules/stats/view/StatsView.js +137 -0
  211. package/dist/src/modules/stats/view/TeamStats/TeamStatsView.js +43 -0
  212. package/dist/src/modules/stats/view/TeamStats/index.js +1 -0
  213. package/dist/src/modules/stats/view/Teams/StatsCarousel.js +83 -0
  214. package/dist/src/modules/stats/view/Teams/StatsProgressBar.js +19 -0
  215. package/dist/src/modules/stats/view/Teams/Teams.js +27 -0
  216. package/dist/src/modules/stats/view/Teams/index.js +2 -0
  217. package/dist/src/modules/stats/view/WinProbability/WinProbability.js +28 -0
  218. package/dist/src/modules/stats/view/WinProbability/index.js +2 -0
  219. package/dist/src/modules/stats/view/components/AthletesStatistics/AthletesStatistics.js +89 -0
  220. package/dist/src/modules/stats/view/components/AthletesStatistics/StatisticTable.js +12 -0
  221. package/dist/src/modules/stats/view/components/AthletesStatistics/index.js +2 -0
  222. package/dist/src/modules/stats/view/index.js +2 -0
  223. package/dist/src/modules/stats/view-model/StatsViewModel.d.ts +5 -0
  224. package/dist/src/modules/stats/view-model/StatsViewModel.js +989 -0
  225. package/dist/src/sentry.js +31 -0
  226. package/dist/src/services/AnalyticsService/AnalyticsService.d.ts +12 -7
  227. package/dist/src/services/AnalyticsService/AnalyticsService.js +402 -0
  228. package/dist/src/services/AnalyticsService/types.js +1 -0
  229. package/dist/src/services/BetsService.js +122 -0
  230. package/dist/src/services/FantasyService.js +92 -0
  231. package/dist/src/services/GlobalService/GlobalService.d.ts +9 -0
  232. package/dist/src/services/GlobalService/PageService.d.ts +9 -0
  233. package/dist/src/services/GlobalService/index.d.ts +2 -0
  234. package/dist/src/services/GlobalService/mocks.d.ts +2 -0
  235. package/dist/src/services/GlobalService/types.d.ts +11 -0
  236. package/dist/src/services/InsightsService/InsightsPayloadBuilder.d.ts +1 -2
  237. package/dist/src/services/InsightsService/InsightsPayloadBuilder.js +386 -0
  238. package/dist/src/services/InsightsService/InsightsService.js +74 -0
  239. package/dist/src/services/NetworkManager/NetworkManager.js +457 -0
  240. package/dist/src/services/NetworkManager/NetworkManagerFactory.js +101 -0
  241. package/dist/src/services/NetworkManager/errors.js +45 -0
  242. package/dist/src/services/NetworkManager/types.js +10 -0
  243. package/dist/src/services/PageService/PageService.d.ts +9 -0
  244. package/dist/src/services/PageService/PageService.js +39 -0
  245. package/dist/src/services/PageService/index.d.ts +2 -0
  246. package/dist/src/services/PageService/index.js +2 -0
  247. package/dist/src/services/PageService/mocks.d.ts +2 -0
  248. package/dist/src/services/PageService/mocks.js +104 -0
  249. package/dist/src/services/PageService/types.d.ts +76 -0
  250. package/dist/src/services/PageService/types.js +1 -0
  251. package/dist/src/services/ShopService.js +46 -0
  252. package/dist/src/services/StaticService/PageService.d.ts +9 -0
  253. package/dist/src/services/StaticService/StaticService.d.ts +3 -1
  254. package/dist/src/services/StaticService/StaticService.js +59 -0
  255. package/dist/src/services/StaticService/index.js +2 -0
  256. package/dist/src/services/StaticService/mocks.js +104 -0
  257. package/dist/src/services/StaticService/types.js +1 -0
  258. package/dist/src/services/StatsService.d.ts +1 -0
  259. package/dist/src/services/StatsService.js +147 -0
  260. package/dist/src/services/ThemeManager/constants.js +47 -0
  261. package/dist/src/services/UserSettingsManager.js +155 -0
  262. package/dist/src/test-polyfills.js +15 -0
  263. package/dist/src/types/OverlayTypes.js +4 -0
  264. package/dist/src/view-models/MaestroEventViewModel.d.ts +1 -2
  265. package/dist/src/view-models/MaestroEventViewModel.js +307 -0
  266. package/dist/src/view-models/OverlayViewModel.js +222 -0
  267. package/dist/src/view-models/PanelManagerViewModel.js +212 -0
  268. package/dist/src/view-models/ViewModel.js +32 -0
  269. package/package.json +1 -1
@@ -0,0 +1,876 @@
1
+ import measureLayout from './measureLayout';
2
+ import { KEY_BACK } from './utils';
3
+ import { DIRECTION_DOWN, KEY_ENTER } from './utils';
4
+ import { DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_UP } from './utils';
5
+ import VisualDebugger from './visualDebugger';
6
+ export const ROOT_FOCUS_KEY = 'SN:ROOT';
7
+ const ADJACENT_SLICE_THRESHOLD = 0.2;
8
+ /**
9
+ * Adjacent slice is 5 times more important than diagonal
10
+ */
11
+ const ADJACENT_SLICE_WEIGHT = 5;
12
+ const DIAGONAL_SLICE_WEIGHT = 1;
13
+ /**
14
+ * Main coordinate distance is 5 times more important
15
+ */
16
+ const MAIN_COORDINATE_WEIGHT = 5;
17
+ const DEFAULT_KEY_MAP = {
18
+ [DIRECTION_LEFT]: [37],
19
+ [DIRECTION_UP]: [38],
20
+ [DIRECTION_RIGHT]: [39],
21
+ [DIRECTION_DOWN]: [40],
22
+ [KEY_ENTER]: [13],
23
+ [KEY_BACK]: [8, 10009, 461],
24
+ };
25
+ const DEBUG_FN_COLORS = ['#0FF', '#FF0', '#F0F'];
26
+ const THROTTLE_OPTIONS = {
27
+ leading: true,
28
+ trailing: false,
29
+ };
30
+ export const getChildClosestToOrigin = (children) => {
31
+ const childrenClosestToOrigin = sortBy(children, ({ layout }) => Math.abs(layout.left) + Math.abs(layout.top));
32
+ return first(childrenClosestToOrigin);
33
+ };
34
+ const throttle = (func, wait, options = {}) => {
35
+ let timeout = null;
36
+ let previous = 0;
37
+ const { leading = true, trailing = true } = options;
38
+ return function (...args) {
39
+ const now = Date.now();
40
+ if (!previous && !leading)
41
+ previous = now;
42
+ const remaining = wait - (now - previous);
43
+ if (remaining <= 0 || remaining > wait) {
44
+ if (timeout) {
45
+ clearTimeout(timeout);
46
+ timeout = null;
47
+ }
48
+ previous = now;
49
+ func.apply(this, args);
50
+ }
51
+ else if (!timeout && trailing) {
52
+ timeout = window.setTimeout(() => {
53
+ previous = leading ? Date.now() : 0;
54
+ timeout = null;
55
+ func.apply(this, args);
56
+ }, remaining);
57
+ }
58
+ };
59
+ };
60
+ const sortBy = (array, iteratee) => [...array].sort((a, b) => {
61
+ const aValue = iteratee(a);
62
+ const bValue = iteratee(b);
63
+ return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
64
+ });
65
+ const first = (array) => array[0];
66
+ const difference = (array, values) => array.filter((x) => !values.includes(x));
67
+ const forEach = (array, iteratee) => array.forEach(iteratee);
68
+ /* eslint-disable no-nested-ternary */
69
+ class SpatialNavigation {
70
+ /**
71
+ * Used to determine the coordinate that will be used to filter items that are over the "edge"
72
+ */
73
+ static getCutoffCoordinate(isVertical, isIncremental, isSibling, layout) {
74
+ const itemX = layout.left;
75
+ const itemY = layout.top;
76
+ const itemWidth = layout.width;
77
+ const itemHeight = layout.height;
78
+ const coordinate = isVertical ? itemY : itemX;
79
+ const itemSize = isVertical ? itemHeight : itemWidth;
80
+ return isIncremental
81
+ ? isSibling
82
+ ? coordinate
83
+ : coordinate + itemSize
84
+ : isSibling
85
+ ? coordinate + itemSize
86
+ : coordinate;
87
+ }
88
+ /**
89
+ * Returns two corners (a and b) coordinates that are used as a reference points
90
+ * Where "a" is always leftmost and topmost corner, and "b" is rightmost bottommost corner
91
+ */
92
+ static getRefCorners(direction, isSibling, layout) {
93
+ const itemX = layout.left;
94
+ const itemY = layout.top;
95
+ const itemWidth = layout.width;
96
+ const itemHeight = layout.height;
97
+ const result = {
98
+ a: {
99
+ x: 0,
100
+ y: 0,
101
+ },
102
+ b: {
103
+ x: 0,
104
+ y: 0,
105
+ },
106
+ };
107
+ switch (direction) {
108
+ case DIRECTION_UP: {
109
+ const y = isSibling ? itemY + itemHeight : itemY;
110
+ result.a = {
111
+ x: itemX,
112
+ y,
113
+ };
114
+ result.b = {
115
+ x: itemX + itemWidth,
116
+ y,
117
+ };
118
+ break;
119
+ }
120
+ case DIRECTION_DOWN: {
121
+ const y = isSibling ? itemY : itemY + itemHeight;
122
+ result.a = {
123
+ x: itemX,
124
+ y,
125
+ };
126
+ result.b = {
127
+ x: itemX + itemWidth,
128
+ y,
129
+ };
130
+ break;
131
+ }
132
+ case DIRECTION_LEFT: {
133
+ const x = isSibling ? itemX + itemWidth : itemX;
134
+ result.a = {
135
+ x,
136
+ y: itemY,
137
+ };
138
+ result.b = {
139
+ x,
140
+ y: itemY + itemHeight,
141
+ };
142
+ break;
143
+ }
144
+ case DIRECTION_RIGHT: {
145
+ const x = isSibling ? itemX : itemX + itemWidth;
146
+ result.a = {
147
+ x,
148
+ y: itemY,
149
+ };
150
+ result.b = {
151
+ x,
152
+ y: itemY + itemHeight,
153
+ };
154
+ break;
155
+ }
156
+ default:
157
+ break;
158
+ }
159
+ return result;
160
+ }
161
+ /**
162
+ * Calculates if the sibling node is intersecting enough with the ref node by the secondary coordinate
163
+ */
164
+ static isAdjacentSlice(refCorners, siblingCorners, isVerticalDirection) {
165
+ const { a: refA, b: refB } = refCorners;
166
+ const { a: siblingA, b: siblingB } = siblingCorners;
167
+ const coordinate = isVerticalDirection ? 'x' : 'y';
168
+ const refCoordinateA = refA[coordinate];
169
+ const refCoordinateB = refB[coordinate];
170
+ const siblingCoordinateA = siblingA[coordinate];
171
+ const siblingCoordinateB = siblingB[coordinate];
172
+ const thresholdDistance = (refCoordinateB - refCoordinateA) * ADJACENT_SLICE_THRESHOLD;
173
+ const intersectionLength = Math.max(0, Math.min(refCoordinateB, siblingCoordinateB) -
174
+ Math.max(refCoordinateA, siblingCoordinateA));
175
+ return intersectionLength >= thresholdDistance;
176
+ }
177
+ static getPrimaryAxisDistance(refCorners, siblingCorners, isVerticalDirection) {
178
+ const { a: refA } = refCorners;
179
+ const { a: siblingA } = siblingCorners;
180
+ const coordinate = isVerticalDirection ? 'y' : 'x';
181
+ return Math.abs(siblingA[coordinate] - refA[coordinate]);
182
+ }
183
+ static getSecondaryAxisDistance(refCorners, siblingCorners, isVerticalDirection) {
184
+ const { a: refA, b: refB } = refCorners;
185
+ const { a: siblingA, b: siblingB } = siblingCorners;
186
+ const coordinate = isVerticalDirection ? 'x' : 'y';
187
+ const refCoordinateA = refA[coordinate];
188
+ const refCoordinateB = refB[coordinate];
189
+ const siblingCoordinateA = siblingA[coordinate];
190
+ const siblingCoordinateB = siblingB[coordinate];
191
+ const distancesToCompare = [];
192
+ distancesToCompare.push(Math.abs(siblingCoordinateA - refCoordinateA));
193
+ distancesToCompare.push(Math.abs(siblingCoordinateA - refCoordinateB));
194
+ distancesToCompare.push(Math.abs(siblingCoordinateB - refCoordinateA));
195
+ distancesToCompare.push(Math.abs(siblingCoordinateB - refCoordinateB));
196
+ return Math.min(...distancesToCompare);
197
+ }
198
+ /**
199
+ * Inspired by: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS_for_TV/TV_remote_control_navigation#Algorithm_design
200
+ * Ref Corners are the 2 corners of the current component in the direction of navigation
201
+ * They used as a base to measure adjacent slices
202
+ */
203
+ sortSiblingsByPriority(siblings, currentLayout, direction, _focusKey) {
204
+ const isVerticalDirection = direction === DIRECTION_DOWN || direction === DIRECTION_UP;
205
+ const refCorners = SpatialNavigation.getRefCorners(direction, false, currentLayout);
206
+ return [...siblings].sort((a, b) => {
207
+ const aCorners = SpatialNavigation.getRefCorners(direction, true, a.layout);
208
+ const bCorners = SpatialNavigation.getRefCorners(direction, true, b.layout);
209
+ const aIsAdjacentSlice = SpatialNavigation.isAdjacentSlice(refCorners, aCorners, isVerticalDirection);
210
+ const bIsAdjacentSlice = SpatialNavigation.isAdjacentSlice(refCorners, bCorners, isVerticalDirection);
211
+ const aPrimaryAxisFunction = aIsAdjacentSlice
212
+ ? SpatialNavigation.getPrimaryAxisDistance
213
+ : SpatialNavigation.getSecondaryAxisDistance;
214
+ const bPrimaryAxisFunction = bIsAdjacentSlice
215
+ ? SpatialNavigation.getPrimaryAxisDistance
216
+ : SpatialNavigation.getSecondaryAxisDistance;
217
+ const aSecondaryAxisFunction = aIsAdjacentSlice
218
+ ? SpatialNavigation.getSecondaryAxisDistance
219
+ : SpatialNavigation.getPrimaryAxisDistance;
220
+ const bSecondaryAxisFunction = bIsAdjacentSlice
221
+ ? SpatialNavigation.getSecondaryAxisDistance
222
+ : SpatialNavigation.getPrimaryAxisDistance;
223
+ const aPrimaryAxisDistance = aPrimaryAxisFunction(refCorners, aCorners, isVerticalDirection);
224
+ const bPrimaryAxisDistance = bPrimaryAxisFunction(refCorners, bCorners, isVerticalDirection);
225
+ const aSecondaryAxisDistance = aSecondaryAxisFunction(refCorners, aCorners, isVerticalDirection);
226
+ const bSecondaryAxisDistance = bSecondaryAxisFunction(refCorners, bCorners, isVerticalDirection);
227
+ const aTotalDistancePoints = aPrimaryAxisDistance * MAIN_COORDINATE_WEIGHT +
228
+ aSecondaryAxisDistance;
229
+ const bTotalDistancePoints = bPrimaryAxisDistance * MAIN_COORDINATE_WEIGHT +
230
+ bSecondaryAxisDistance;
231
+ const aPriority = (aTotalDistancePoints + 1) /
232
+ (aIsAdjacentSlice ? ADJACENT_SLICE_WEIGHT : DIAGONAL_SLICE_WEIGHT);
233
+ const bPriority = (bTotalDistancePoints + 1) /
234
+ (bIsAdjacentSlice ? ADJACENT_SLICE_WEIGHT : DIAGONAL_SLICE_WEIGHT);
235
+ return aPriority - bPriority;
236
+ });
237
+ }
238
+ constructor(options = {}) {
239
+ Object.defineProperty(this, "focusableComponents", {
240
+ enumerable: true,
241
+ configurable: true,
242
+ writable: true,
243
+ value: void 0
244
+ });
245
+ Object.defineProperty(this, "focusKey", {
246
+ enumerable: true,
247
+ configurable: true,
248
+ writable: true,
249
+ value: void 0
250
+ });
251
+ Object.defineProperty(this, "parentsHavingFocusedChild", {
252
+ enumerable: true,
253
+ configurable: true,
254
+ writable: true,
255
+ value: void 0
256
+ });
257
+ Object.defineProperty(this, "nativeMode", {
258
+ enumerable: true,
259
+ configurable: true,
260
+ writable: true,
261
+ value: void 0
262
+ });
263
+ Object.defineProperty(this, "throttle", {
264
+ enumerable: true,
265
+ configurable: true,
266
+ writable: true,
267
+ value: void 0
268
+ });
269
+ Object.defineProperty(this, "throttleKeypresses", {
270
+ enumerable: true,
271
+ configurable: true,
272
+ writable: true,
273
+ value: void 0
274
+ });
275
+ Object.defineProperty(this, "pressedKeys", {
276
+ enumerable: true,
277
+ configurable: true,
278
+ writable: true,
279
+ value: void 0
280
+ });
281
+ Object.defineProperty(this, "paused", {
282
+ enumerable: true,
283
+ configurable: true,
284
+ writable: true,
285
+ value: void 0
286
+ });
287
+ Object.defineProperty(this, "keyDownEventListener", {
288
+ enumerable: true,
289
+ configurable: true,
290
+ writable: true,
291
+ value: void 0
292
+ });
293
+ Object.defineProperty(this, "keyUpEventListener", {
294
+ enumerable: true,
295
+ configurable: true,
296
+ writable: true,
297
+ value: void 0
298
+ });
299
+ Object.defineProperty(this, "keyMap", {
300
+ enumerable: true,
301
+ configurable: true,
302
+ writable: true,
303
+ value: void 0
304
+ });
305
+ Object.defineProperty(this, "debug", {
306
+ enumerable: true,
307
+ configurable: true,
308
+ writable: true,
309
+ value: void 0
310
+ });
311
+ Object.defineProperty(this, "visualDebugger", {
312
+ enumerable: true,
313
+ configurable: true,
314
+ writable: true,
315
+ value: void 0
316
+ });
317
+ Object.defineProperty(this, "logIndex", {
318
+ enumerable: true,
319
+ configurable: true,
320
+ writable: true,
321
+ value: void 0
322
+ });
323
+ this.focusableComponents = {};
324
+ this.focusKey = null;
325
+ this.parentsHavingFocusedChild = [];
326
+ this.nativeMode = false;
327
+ this.throttle = 0;
328
+ this.throttleKeypresses = false;
329
+ this.pressedKeys = {};
330
+ this.paused = true;
331
+ this.keyDownEventListener = null;
332
+ this.keyUpEventListener = null;
333
+ this.keyMap = DEFAULT_KEY_MAP;
334
+ this.debug = false;
335
+ this.visualDebugger = null;
336
+ this.logIndex = 0;
337
+ this.onKeyEvent = this.onKeyEvent.bind(this);
338
+ this.pause = this.pause.bind(this);
339
+ this.resume = this.resume.bind(this);
340
+ this.setFocus = this.setFocus.bind(this);
341
+ this.updateAllLayouts = this.updateAllLayouts.bind(this);
342
+ this.navigateByDirection = this.navigateByDirection.bind(this);
343
+ this.init = this.init.bind(this);
344
+ this.setKeyMap = this.setKeyMap.bind(this);
345
+ this.init(options);
346
+ }
347
+ init({ debug = false, visualDebug = false, nativeMode = false, throttle = 0, throttleKeypresses = false, } = {}) {
348
+ this.nativeMode = nativeMode;
349
+ this.throttleKeypresses = throttleKeypresses;
350
+ this.debug = debug;
351
+ if (!this.nativeMode) {
352
+ if (Number.isInteger(throttle) && throttle > 0) {
353
+ this.throttle = throttle;
354
+ }
355
+ this.bindEventHandlers();
356
+ if (visualDebug) {
357
+ this.visualDebugger = new VisualDebugger();
358
+ this.startDrawLayouts();
359
+ }
360
+ }
361
+ }
362
+ startDrawLayouts() {
363
+ const draw = () => {
364
+ requestAnimationFrame(() => {
365
+ this.visualDebugger?.clearLayouts();
366
+ Object.entries(this.focusableComponents).forEach(([focusKey, component]) => {
367
+ this.visualDebugger?.drawLayout(component.layout, focusKey, component.parentFocusKey);
368
+ });
369
+ draw();
370
+ });
371
+ };
372
+ draw();
373
+ }
374
+ destroy() {
375
+ this.unbindEventHandlers();
376
+ }
377
+ getEventType(keyCode) {
378
+ return Object.keys(this.getKeyMap()).find((key) => this.getKeyMap()[key].includes(keyCode));
379
+ }
380
+ bindEventHandlers() {
381
+ if (typeof window !== 'undefined' && window.addEventListener) {
382
+ const keyDownHandler = (event) => {
383
+ if (this.paused === true) {
384
+ return;
385
+ }
386
+ if (this.debug) {
387
+ this.logIndex += 1;
388
+ }
389
+ const eventType = this.getEventType(event.keyCode);
390
+ if (!eventType) {
391
+ return;
392
+ }
393
+ this.pressedKeys[eventType] = this.pressedKeys[eventType]
394
+ ? this.pressedKeys[eventType] + 1
395
+ : 1;
396
+ event.preventDefault();
397
+ event.stopPropagation();
398
+ const details = {
399
+ pressedKeys: this.pressedKeys,
400
+ };
401
+ if (this.focusKey && this.getNodeLayoutByFocusKey(this.focusKey)) {
402
+ details.layout = this.getNodeLayoutByFocusKey(this.focusKey);
403
+ }
404
+ if (eventType === KEY_ENTER && this.focusKey) {
405
+ this.onEnterPress(details);
406
+ return;
407
+ }
408
+ if (eventType === KEY_BACK && this.focusKey) {
409
+ this.onBackPress(details);
410
+ return;
411
+ }
412
+ const preventDefaultNavigation = this.onArrowPress(eventType, details) === false;
413
+ if (preventDefaultNavigation) {
414
+ this.log('keyDownEventListener', 'default navigation prevented');
415
+ this.visualDebugger?.clear();
416
+ }
417
+ else {
418
+ this.onKeyEvent(event);
419
+ }
420
+ };
421
+ this.keyDownEventListener = this.throttle
422
+ ? throttle(keyDownHandler, this.throttle, THROTTLE_OPTIONS)
423
+ : keyDownHandler;
424
+ this.keyUpEventListener = (event) => {
425
+ const eventType = this.getEventType(event.keyCode);
426
+ if (eventType) {
427
+ delete this.pressedKeys[eventType];
428
+ }
429
+ if (this.throttle && !this.throttleKeypresses) {
430
+ // @ts-ignore
431
+ this.keyDownEventListener?.cancel?.();
432
+ }
433
+ if (eventType === KEY_ENTER && this.focusKey) {
434
+ this.onEnterRelease();
435
+ }
436
+ };
437
+ window.addEventListener('keyup', this.keyUpEventListener);
438
+ window.addEventListener('keydown', this.keyDownEventListener);
439
+ }
440
+ }
441
+ unbindEventHandlers() {
442
+ if (typeof window !== 'undefined' && window.removeEventListener) {
443
+ window.removeEventListener('keydown', this.keyDownEventListener);
444
+ this.keyDownEventListener = null;
445
+ if (this.throttle) {
446
+ window.removeEventListener('keyup', this.keyUpEventListener);
447
+ this.keyUpEventListener = null;
448
+ }
449
+ }
450
+ }
451
+ onEnterPress(details) {
452
+ const component = this.focusableComponents[this.focusKey];
453
+ if (!component) {
454
+ this.log('onEnterPress', 'noComponent');
455
+ return;
456
+ }
457
+ if (!component.focusable) {
458
+ this.log('onEnterPress', 'componentNotFocusable');
459
+ return;
460
+ }
461
+ component.onEnterPressHandler?.(details);
462
+ }
463
+ onBackPress(details) {
464
+ const component = this.focusableComponents[this.focusKey];
465
+ if (!component) {
466
+ this.log('onBackPress', 'noComponent');
467
+ return;
468
+ }
469
+ if (!component.focusable) {
470
+ this.log('onBackPress', 'componentNotFocusable');
471
+ return;
472
+ }
473
+ component.onBackPressHandler?.(details);
474
+ }
475
+ onEnterRelease() {
476
+ const component = this.focusableComponents[this.focusKey];
477
+ if (!component) {
478
+ this.log('onEnterRelease', 'noComponent');
479
+ return;
480
+ }
481
+ if (!component.focusable) {
482
+ this.log('onEnterRelease', 'componentNotFocusable');
483
+ return;
484
+ }
485
+ component.onEnterReleaseHandler?.();
486
+ }
487
+ onArrowPress(direction, details) {
488
+ const component = this.focusableComponents[this.focusKey];
489
+ if (!component) {
490
+ this.log('onArrowPress', 'noComponent');
491
+ return undefined;
492
+ }
493
+ return component.onArrowPressHandler?.(direction, details);
494
+ }
495
+ navigateByDirection(direction, details = {}) {
496
+ if (this.paused === true) {
497
+ return;
498
+ }
499
+ const validDirections = [
500
+ DIRECTION_DOWN,
501
+ DIRECTION_UP,
502
+ DIRECTION_LEFT,
503
+ DIRECTION_RIGHT,
504
+ ];
505
+ if (validDirections.includes(direction)) {
506
+ this.log('navigateByDirection', 'direction', direction);
507
+ this.smartNavigate(direction, null, details);
508
+ }
509
+ else {
510
+ this.log('navigateByDirection', `Invalid direction. You passed: \`${direction}\`, but you can use only these: `, validDirections);
511
+ }
512
+ }
513
+ onKeyEvent(event) {
514
+ this.visualDebugger?.clear();
515
+ const direction = Object.keys(this.getKeyMap()).find((key) => this.getKeyMap()[key].includes(event.keyCode));
516
+ if (direction) {
517
+ this.smartNavigate(direction, null, { event });
518
+ }
519
+ }
520
+ smartNavigate(direction, fromParentFocusKey, details) {
521
+ this.log('smartNavigate', 'direction', direction);
522
+ this.log('smartNavigate', 'fromParentFocusKey', fromParentFocusKey);
523
+ this.log('smartNavigate', 'this.focusKey', this.focusKey);
524
+ if (!this.nativeMode && !fromParentFocusKey) {
525
+ Object.values(this.focusableComponents).forEach((component) => {
526
+ component.layoutUpdated = false;
527
+ });
528
+ }
529
+ const currentComponent = this.focusableComponents[fromParentFocusKey || this.focusKey];
530
+ this.log('smartNavigate', 'currentComponent', currentComponent ? currentComponent.focusKey : undefined, currentComponent ? currentComponent.node : undefined);
531
+ if (currentComponent) {
532
+ this.updateLayout(currentComponent.focusKey);
533
+ const { parentFocusKey, focusKey, layout } = currentComponent;
534
+ const isVerticalDirection = direction === DIRECTION_DOWN || direction === DIRECTION_UP;
535
+ const isIncrementalDirection = direction === DIRECTION_DOWN || direction === DIRECTION_RIGHT;
536
+ const currentCutoffCoordinate = SpatialNavigation.getCutoffCoordinate(isVerticalDirection, isIncrementalDirection, false, layout);
537
+ /**
538
+ * Get only the siblings with the coords on the way of our moving direction
539
+ */
540
+ const siblings = Object.values(this.focusableComponents).filter((component) => {
541
+ if (component.parentFocusKey === parentFocusKey &&
542
+ component.focusable) {
543
+ this.updateLayout(component.focusKey);
544
+ const siblingCutoffCoordinate = SpatialNavigation.getCutoffCoordinate(isVerticalDirection, isIncrementalDirection, true, component.layout);
545
+ return isIncrementalDirection
546
+ ? siblingCutoffCoordinate >= currentCutoffCoordinate
547
+ : siblingCutoffCoordinate <= currentCutoffCoordinate;
548
+ }
549
+ return false;
550
+ });
551
+ if (this.debug) {
552
+ this.log('smartNavigate', 'siblings', siblings);
553
+ }
554
+ if (siblings.length > 0) {
555
+ const sortedSiblings = this.sortSiblingsByPriority(siblings, layout, direction, focusKey);
556
+ if (this.debug) {
557
+ this.log('smartNavigate', 'sortedSiblings', sortedSiblings);
558
+ }
559
+ const nextComponent = sortedSiblings[0];
560
+ if (nextComponent) {
561
+ this.setFocus(nextComponent.focusKey, details);
562
+ }
563
+ }
564
+ else {
565
+ this.log('smartNavigate', 'noSiblings');
566
+ if (parentFocusKey) {
567
+ this.smartNavigate(direction, parentFocusKey, details);
568
+ }
569
+ }
570
+ }
571
+ }
572
+ saveLastFocusedChildKey(component, focusKey) {
573
+ if (component) {
574
+ this.log('saveLastFocusedChildKey', `${component.focusKey} lastFocusedChildKey set`, focusKey);
575
+ component.lastFocusedChildKey = focusKey;
576
+ }
577
+ }
578
+ log(functionName, debugString, ...rest) {
579
+ if (this.debug) {
580
+ console.log(`%c${functionName}%c${debugString}`, `background: ${DEBUG_FN_COLORS[this.logIndex % DEBUG_FN_COLORS.length]}; color: black; padding: 1px 5px;`, 'background: #333; color: #BADA55; padding: 1px 5px;', ...rest);
581
+ }
582
+ }
583
+ getNextFocusKey(targetFocusKey) {
584
+ this.log('getNextFocusKey', 'targetFocusKey', targetFocusKey);
585
+ const targetComponent = this.focusableComponents[targetFocusKey];
586
+ if (!targetComponent) {
587
+ return targetFocusKey;
588
+ }
589
+ const { trackChildren } = targetComponent;
590
+ if (!trackChildren) {
591
+ return targetFocusKey;
592
+ }
593
+ const children = Object.values(this.focusableComponents).filter((component) => component.parentFocusKey === targetFocusKey && component.focusable);
594
+ if (children.length === 0) {
595
+ return targetFocusKey;
596
+ }
597
+ const { lastFocusedChildKey, preferredChildFocusKey } = targetComponent;
598
+ /**
599
+ * First of all trying to focus last focused child
600
+ */
601
+ if (lastFocusedChildKey &&
602
+ !targetComponent.forgetLastFocusedChild &&
603
+ this.isParticipatingFocusableComponent(lastFocusedChildKey)) {
604
+ this.log('getNextFocusKey', 'lastFocusedChildKey will be focused', lastFocusedChildKey);
605
+ return this.getNextFocusKey(lastFocusedChildKey);
606
+ }
607
+ /**
608
+ * If there is no lastFocusedChild, trying to focus the preferred focused key
609
+ */
610
+ if (preferredChildFocusKey &&
611
+ this.isParticipatingFocusableComponent(preferredChildFocusKey)) {
612
+ this.log('getNextFocusKey', 'preferredChildFocusKey will be focused', preferredChildFocusKey);
613
+ return this.getNextFocusKey(preferredChildFocusKey);
614
+ }
615
+ /**
616
+ * Otherwise, trying to focus something by coordinates
617
+ */
618
+ children.forEach((component) => this.updateLayout(component.focusKey));
619
+ const { focusKey: childKey } = getChildClosestToOrigin(children);
620
+ this.log('getNextFocusKey', 'childKey will be focused', childKey);
621
+ return this.getNextFocusKey(childKey);
622
+ }
623
+ addFocusable({ focusKey, node, parentFocusKey, onEnterPressHandler, onEnterReleaseHandler, onBackPressHandler, onArrowPressHandler, onBecameFocusedHandler, onBecameBlurredHandler, forgetLastFocusedChild, trackChildren, onUpdateFocus, onUpdateHasFocusedChild, preferredChildFocusKey, autoRestoreFocus, focusable = true, blockNavigationOut, }) {
624
+ this.focusableComponents[focusKey] = {
625
+ focusKey,
626
+ node,
627
+ parentFocusKey,
628
+ onEnterPressHandler,
629
+ onEnterReleaseHandler,
630
+ onBackPressHandler,
631
+ onArrowPressHandler,
632
+ onBecameFocusedHandler,
633
+ onBecameBlurredHandler,
634
+ onUpdateFocus,
635
+ onUpdateHasFocusedChild,
636
+ forgetLastFocusedChild,
637
+ trackChildren,
638
+ lastFocusedChildKey: null,
639
+ preferredChildFocusKey,
640
+ focusable,
641
+ blockNavigationOut,
642
+ autoRestoreFocus,
643
+ layout: {
644
+ x: 0,
645
+ y: 0,
646
+ width: 0,
647
+ height: 0,
648
+ left: 0,
649
+ top: 0,
650
+ node,
651
+ },
652
+ layoutUpdated: false,
653
+ };
654
+ if (this.nativeMode) {
655
+ return;
656
+ }
657
+ this.updateLayout(focusKey);
658
+ /**
659
+ * If for some reason this component was already focused before it was added, call the update
660
+ */
661
+ if (focusKey === this.focusKey) {
662
+ this.setFocus(focusKey);
663
+ }
664
+ }
665
+ removeFocusable({ focusKey }) {
666
+ const componentToRemove = this.focusableComponents[focusKey];
667
+ if (componentToRemove) {
668
+ const { parentFocusKey } = componentToRemove;
669
+ Reflect.deleteProperty(this.focusableComponents, focusKey);
670
+ const parentComponent = this.focusableComponents[parentFocusKey];
671
+ const wasFocused = focusKey === this.focusKey;
672
+ // If a component was unmounted and it was focused, we need to clear focusKey if the focus is in the Client App so the focus don't get lost.
673
+ if (wasFocused) {
674
+ if (this.paused) {
675
+ this.focusKey = null;
676
+ }
677
+ else {
678
+ // if its not paused, we need to focus on a component that is visible so we don't lose focus
679
+ this.setFocus('PANEL_NAVIGATION');
680
+ }
681
+ }
682
+ /**
683
+ * If the component was stored as lastFocusedChild, clear lastFocusedChildKey from parent
684
+ */
685
+ parentComponent &&
686
+ parentComponent.lastFocusedChildKey === focusKey &&
687
+ (parentComponent.lastFocusedChildKey = null);
688
+ if (this.nativeMode) {
689
+ return;
690
+ }
691
+ /**
692
+ * If the component was also focused at this time, focus another one
693
+ */
694
+ if (wasFocused &&
695
+ parentComponent &&
696
+ parentComponent.autoRestoreFocus &&
697
+ !this.paused) {
698
+ this.setFocus(parentFocusKey);
699
+ }
700
+ }
701
+ }
702
+ getNodeLayoutByFocusKey(focusKey) {
703
+ const component = this.focusableComponents[focusKey];
704
+ if (component) {
705
+ this.updateLayout(component.focusKey);
706
+ return component.layout;
707
+ }
708
+ return null;
709
+ }
710
+ setCurrentFocusedKey(newFocusKey, details) {
711
+ if (this.isFocusableComponent(this.focusKey) &&
712
+ newFocusKey !== this.focusKey) {
713
+ const oldComponent = this.focusableComponents[this.focusKey];
714
+ const parentComponent = this.focusableComponents[oldComponent.parentFocusKey];
715
+ this.saveLastFocusedChildKey(parentComponent, this.focusKey);
716
+ oldComponent.onUpdateFocus?.(false);
717
+ oldComponent.onBecameBlurredHandler?.(this.getNodeLayoutByFocusKey(this.focusKey), details);
718
+ }
719
+ this.focusKey = newFocusKey;
720
+ if (this.isFocusableComponent(this.focusKey)) {
721
+ const newComponent = this.focusableComponents[this.focusKey];
722
+ newComponent.onUpdateFocus?.(true);
723
+ newComponent.onBecameFocusedHandler?.(this.getNodeLayoutByFocusKey(this.focusKey), details);
724
+ }
725
+ }
726
+ updateParentsHasFocusedChild(focusKey, details) {
727
+ const parents = [];
728
+ let currentComponent = this.focusableComponents[focusKey];
729
+ /**
730
+ * Recursively iterate the tree up and find all the parents' focus keys
731
+ */
732
+ while (currentComponent) {
733
+ const { parentFocusKey } = currentComponent;
734
+ const parentComponent = this.focusableComponents[parentFocusKey];
735
+ if (parentComponent) {
736
+ const { focusKey: currentParentFocusKey } = parentComponent;
737
+ parents.push(currentParentFocusKey);
738
+ }
739
+ currentComponent = parentComponent;
740
+ }
741
+ const parentsToRemoveFlag = difference(this.parentsHavingFocusedChild, parents);
742
+ const parentsToAddFlag = difference(parents, this.parentsHavingFocusedChild);
743
+ forEach(parentsToRemoveFlag, (parentFocusKey) => {
744
+ const parentComponent = this.focusableComponents[parentFocusKey];
745
+ parentComponent &&
746
+ parentComponent.trackChildren &&
747
+ parentComponent.onUpdateHasFocusedChild?.(false);
748
+ this.onIntermediateNodeBecameBlurred(parentFocusKey, details);
749
+ });
750
+ forEach(parentsToAddFlag, (parentFocusKey) => {
751
+ const parentComponent = this.focusableComponents[parentFocusKey];
752
+ parentComponent &&
753
+ parentComponent.trackChildren &&
754
+ parentComponent.onUpdateHasFocusedChild?.(true);
755
+ this.onIntermediateNodeBecameFocused(parentFocusKey, details);
756
+ });
757
+ this.parentsHavingFocusedChild = parents;
758
+ }
759
+ updateParentsLastFocusedChild(focusKey) {
760
+ let currentComponent = this.focusableComponents[focusKey];
761
+ /**
762
+ * Recursively iterate the tree up and update all the parent's lastFocusedChild
763
+ */
764
+ while (currentComponent) {
765
+ const { parentFocusKey } = currentComponent;
766
+ const parentComponent = this.focusableComponents[parentFocusKey];
767
+ if (parentComponent) {
768
+ this.saveLastFocusedChildKey(parentComponent, currentComponent.focusKey);
769
+ }
770
+ currentComponent = parentComponent;
771
+ }
772
+ }
773
+ getKeyMap() {
774
+ return this.keyMap;
775
+ }
776
+ setKeyMap(keyMap) {
777
+ this.keyMap = {
778
+ ...this.getKeyMap(),
779
+ ...keyMap,
780
+ };
781
+ }
782
+ isFocusableComponent(focusKey) {
783
+ return !!this.focusableComponents[focusKey];
784
+ }
785
+ /**
786
+ * Checks whether the focusableComponent is actually participating in spatial navigation (in other words, is a
787
+ * 'focusable' focusableComponent). Seems less confusing than calling it isFocusableFocusableComponent()
788
+ */
789
+ isParticipatingFocusableComponent(focusKey) {
790
+ return (this.isFocusableComponent(focusKey) &&
791
+ this.focusableComponents[focusKey].focusable);
792
+ }
793
+ onIntermediateNodeBecameFocused(focusKey, details) {
794
+ this.isParticipatingFocusableComponent(focusKey) &&
795
+ this.focusableComponents[focusKey].onBecameFocusedHandler?.(this.getNodeLayoutByFocusKey(focusKey), details);
796
+ }
797
+ onIntermediateNodeBecameBlurred(focusKey, details) {
798
+ this.isParticipatingFocusableComponent(focusKey) &&
799
+ this.focusableComponents[focusKey].onBecameBlurredHandler?.(this.getNodeLayoutByFocusKey(focusKey), details);
800
+ }
801
+ pause() {
802
+ this.parentsHavingFocusedChild = [];
803
+ this.pressedKeys = {};
804
+ this.log('pause', 'paused');
805
+ this.visualDebugger?.clear();
806
+ this.visualDebugger?.clearLayouts();
807
+ this.getFocusableComponents().forEach((component) => {
808
+ component.onUpdateFocus?.(false);
809
+ });
810
+ this.paused = true;
811
+ }
812
+ resume() {
813
+ this.paused = false;
814
+ }
815
+ setFocus(targetFocusKey, details = {}) {
816
+ const lastFocusedKey = this.focusKey;
817
+ const newFocusKey = this.getNextFocusKey(targetFocusKey);
818
+ this.log('setFocus', 'newFocusKey', newFocusKey);
819
+ this.setCurrentFocusedKey(newFocusKey, details);
820
+ this.updateParentsHasFocusedChild(newFocusKey, details);
821
+ this.updateParentsLastFocusedChild(lastFocusedKey);
822
+ }
823
+ updateAllLayouts() {
824
+ if (this.nativeMode) {
825
+ return;
826
+ }
827
+ Object.entries(this.focusableComponents).forEach(([focusKey]) => {
828
+ this.updateLayout(focusKey);
829
+ });
830
+ }
831
+ updateLayout(focusKey) {
832
+ const component = this.focusableComponents[focusKey];
833
+ if (!component || this.nativeMode || component.layoutUpdated) {
834
+ return;
835
+ }
836
+ const { node } = component;
837
+ measureLayout(node, (x, y, width, height, left, top) => {
838
+ component.layout = {
839
+ x,
840
+ y,
841
+ width,
842
+ height,
843
+ left,
844
+ top,
845
+ node,
846
+ };
847
+ });
848
+ }
849
+ updateFocusable(focusKey, { node, preferredChildFocusKey, focusable, blockNavigationOut, }) {
850
+ if (this.nativeMode) {
851
+ return;
852
+ }
853
+ const component = this.focusableComponents[focusKey];
854
+ if (component) {
855
+ component.preferredChildFocusKey = preferredChildFocusKey;
856
+ component.focusable = focusable ?? component.focusable;
857
+ component.blockNavigationOut = blockNavigationOut;
858
+ if (node) {
859
+ component.node = node;
860
+ }
861
+ }
862
+ }
863
+ isNativeMode() {
864
+ return this.nativeMode;
865
+ }
866
+ getFocusableComponents() {
867
+ return Object.values(this.focusableComponents);
868
+ }
869
+ getFocusableComponentsByParentFocusKey(parentFocusKey) {
870
+ return Object.values(this.focusableComponents).filter((component) => component.parentFocusKey === parentFocusKey);
871
+ }
872
+ }
873
+ /**
874
+ * Export singleton
875
+ */
876
+ export default SpatialNavigation;