@maestro_io/maestro-web-sdk 2.1.3 → 2.2.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 (171) hide show
  1. package/dist/MaestroEventDelegate.d.ts +1 -0
  2. package/dist/components/atoms/SvgIcon/Icon.d.ts +0 -2
  3. package/dist/components/core/ScrollableContainer/ScrollableContainer.d.ts +3 -1
  4. package/dist/external/spatial-navigation/spatialNavigation.d.ts +1 -1
  5. package/dist/interfaces/IMaestroEvent.d.ts +14 -0
  6. package/dist/interfaces/IMaestroEventDelegate.d.ts +19 -6
  7. package/dist/maestro-web-sdk.umd.js +3 -3
  8. package/dist/maestro-web-sdk.umd.js.map +1 -1
  9. package/dist/models/IPanel.d.ts +1 -2
  10. package/dist/modules/stats/interfaces/IStats.d.ts +38 -12
  11. package/dist/modules/stats/mocks.d.ts +0 -80
  12. package/dist/modules/stats/utils/driveSummaryUtils.d.ts +18 -0
  13. package/dist/modules/stats/view/ActiveAthletes/ActiveAthletes.d.ts +6 -19
  14. package/dist/modules/stats/view/DriveSummary/DrivePlayCard.d.ts +26 -0
  15. package/dist/modules/stats/view/DriveSummary/DriveSummary.d.ts +24 -0
  16. package/dist/modules/stats/view/DriveSummary/index.d.ts +2 -0
  17. package/dist/modules/stats/view/GameLeaders/GameLeaders.d.ts +15 -4
  18. package/dist/modules/stats/view/MatchPredictor/MatchPredictorView.d.ts +13 -48
  19. package/dist/modules/stats/view/RecentGames/RecentGames.d.ts +10 -21
  20. package/dist/modules/stats/view/StatsItemView.d.ts +19 -0
  21. package/dist/modules/stats/view/StatsView.d.ts +3 -3
  22. package/dist/modules/stats/view/TeamStats/TeamStatsView.d.ts +2 -4
  23. package/dist/modules/stats/view/Teams/Teams.d.ts +10 -26
  24. package/dist/modules/stats/view/WinProbability/WinProbability.d.ts +10 -21
  25. package/dist/modules/stats/view-model/StatsViewModel.d.ts +149 -5
  26. package/package.json +1 -1
  27. package/dist/MaestroEventDelegate.js +0 -60
  28. package/dist/components/atoms/BaseButton/BaseButton.js +0 -11
  29. package/dist/components/atoms/BaseButton/index.js +0 -2
  30. package/dist/components/atoms/Image/index.js +0 -30
  31. package/dist/components/atoms/Rive/index.js +0 -53
  32. package/dist/components/atoms/SvgIcon/BetGeneralIcon.js +0 -9
  33. package/dist/components/atoms/SvgIcon/BetsIcon.js +0 -11
  34. package/dist/components/atoms/SvgIcon/BetsWarningIcon.js +0 -11
  35. package/dist/components/atoms/SvgIcon/CheckmarkIcon.js +0 -6
  36. package/dist/components/atoms/SvgIcon/CollapseIcon.js +0 -6
  37. package/dist/components/atoms/SvgIcon/ExpandIcon.js +0 -6
  38. package/dist/components/atoms/SvgIcon/FantasyIcon.d.ts +0 -6
  39. package/dist/components/atoms/SvgIcon/FantasyIcon.js +0 -8
  40. package/dist/components/atoms/SvgIcon/FlameIcon.js +0 -11
  41. package/dist/components/atoms/SvgIcon/Icon.js +0 -42
  42. package/dist/components/atoms/SvgIcon/KeyPlaysIcon.js +0 -10
  43. package/dist/components/atoms/SvgIcon/MobilePhoneIcon.js +0 -11
  44. package/dist/components/atoms/SvgIcon/PlayIcon.js +0 -8
  45. package/dist/components/atoms/SvgIcon/StatsIcon.js +0 -12
  46. package/dist/components/atoms/SvgIcon/TimesIcon.js +0 -6
  47. package/dist/components/atoms/SvgIcon/WarningIcon.js +0 -8
  48. package/dist/components/atoms/SvgIcon/index.js +0 -2
  49. package/dist/components/core/App/App.js +0 -41
  50. package/dist/components/core/App/index.js +0 -2
  51. package/dist/components/core/OverlayContainer.js +0 -58
  52. package/dist/components/core/PanelManager/PanelManager.js +0 -80
  53. package/dist/components/core/PanelManager/index.js +0 -2
  54. package/dist/components/core/ScrollableContainer/FocusableItem.js +0 -38
  55. package/dist/components/core/ScrollableContainer/ScrollableContainer.js +0 -166
  56. package/dist/components/core/ScrollableContainer/index.js +0 -2
  57. package/dist/components/molecules/ActionButton/ActionButton.js +0 -10
  58. package/dist/components/molecules/ActionButton/index.js +0 -2
  59. package/dist/components/molecules/Overlay/Overlay.js +0 -139
  60. package/dist/components/molecules/Overlay/index.js +0 -2
  61. package/dist/components/molecules/PanelNavButton/PanelNavButton.js +0 -40
  62. package/dist/components/molecules/PanelNavButton/index.js +0 -2
  63. package/dist/components/molecules/SegmentButton/SegmentButton.js +0 -10
  64. package/dist/components/molecules/SegmentButton/index.js +0 -2
  65. package/dist/components/molecules/WatchButton/WatchButton.js +0 -15
  66. package/dist/components/molecules/WatchButton/index.js +0 -2
  67. package/dist/components/organisms/PanelNavigation/PanelNavigation.js +0 -71
  68. package/dist/components/organisms/PanelNavigation/index.js +0 -2
  69. package/dist/components/organisms/SegmentController/SegmentController.js +0 -68
  70. package/dist/components/organisms/SegmentController/index.js +0 -2
  71. package/dist/external/AxiosNetworkClient.js +0 -83
  72. package/dist/external/LocalStorageCacheManager.js +0 -213
  73. package/dist/external/ReactRenderer.js +0 -86
  74. package/dist/external/mocks/MockExternalNetworkClient.js +0 -161
  75. package/dist/external/ports/cacheManager.js +0 -1
  76. package/dist/external/ports/networkClient.js +0 -1
  77. package/dist/external/ports/renderer.js +0 -1
  78. package/dist/external/spatial-navigation/index.js +0 -3
  79. package/dist/external/spatial-navigation/measureLayout.js +0 -30
  80. package/dist/external/spatial-navigation/spatialNavigation.js +0 -864
  81. package/dist/external/spatial-navigation/utils.js +0 -6
  82. package/dist/external/spatial-navigation/visualDebugger.js +0 -76
  83. package/dist/external/spatial-navigation/withFocusable.js +0 -245
  84. package/dist/helpers/Observable.js +0 -37
  85. package/dist/helpers/deliver-focus.d.ts +0 -5
  86. package/dist/helpers/deliver-focus.js +0 -12
  87. package/dist/index.js +0 -192
  88. package/dist/interfaces/IMaestroEvent.js +0 -6
  89. package/dist/interfaces/IMaestroEventDelegate.js +0 -1
  90. package/dist/interfaces/IMaestroManager.js +0 -1
  91. package/dist/models/IModel.js +0 -1
  92. package/dist/models/IPanel.js +0 -53
  93. package/dist/models/ITheme.js +0 -20
  94. package/dist/models/IWinningBet.js +0 -1
  95. package/dist/modules/bets/interfaces/IBets.js +0 -1
  96. package/dist/modules/bets/mocks.js +0 -572
  97. package/dist/modules/bets/view/BetsView.js +0 -116
  98. package/dist/modules/bets/view/HotProps/HotPropsView.js +0 -139
  99. package/dist/modules/bets/view/SixPack/SixPackView.js +0 -103
  100. package/dist/modules/bets/view/TabLoadFailureView.js +0 -12
  101. package/dist/modules/bets/view/UserBets/BetResult/BetResult.js +0 -6
  102. package/dist/modules/bets/view/UserBets/BetsCard/BetsCard.js +0 -122
  103. package/dist/modules/bets/view/UserBets/BetsCard/BetsCardEvent.js +0 -34
  104. package/dist/modules/bets/view/UserBets/BetsCard/BetsCardFooter.js +0 -22
  105. package/dist/modules/bets/view/UserBets/Boost/Boost.js +0 -6
  106. package/dist/modules/bets/view/UserBets/OutcomeBadge/OutcomeBadge.js +0 -9
  107. package/dist/modules/bets/view/UserBets/Parlay/Parlay.js +0 -13
  108. package/dist/modules/bets/view/UserBets/Straight/Straight.js +0 -6
  109. package/dist/modules/bets/view/UserBets/Teaser/Teaser.js +0 -13
  110. package/dist/modules/bets/view/UserBets/UserBetsView.js +0 -114
  111. package/dist/modules/bets/view/components/Bet/Bet.js +0 -32
  112. package/dist/modules/bets/view/components/BetPill/BetPill.js +0 -6
  113. package/dist/modules/bets/view/components/BetsCard/BetsCard.js +0 -15
  114. package/dist/modules/bets/view/components/BetsCard/BetsCardHeader.js +0 -20
  115. package/dist/modules/bets/view/components/GamblingDisclaimer/GamblingDisclaimer.js +0 -14
  116. package/dist/modules/bets/view/components/LocationWarning/LocationWarning.js +0 -16
  117. package/dist/modules/bets/view/components/PromoCodeBanner/PromoCodeBanner.js +0 -63
  118. package/dist/modules/bets/view/components/PromoCodeBanner/index.js +0 -1
  119. package/dist/modules/bets/view-model/BetsViewModel.js +0 -767
  120. package/dist/modules/key-plays/constants.js +0 -292
  121. package/dist/modules/key-plays/interfaces/IKeyPlays.js +0 -14
  122. package/dist/modules/key-plays/interfaces/IKeyPlaysResponse.js +0 -1
  123. package/dist/modules/key-plays/view/KeyPlayCardScoreTagView.js +0 -13
  124. package/dist/modules/key-plays/view/KeyPlayCardView.js +0 -120
  125. package/dist/modules/key-plays/view/KeyPlayErrorStateView.js +0 -58
  126. package/dist/modules/key-plays/view/KeyPlaysView.js +0 -166
  127. package/dist/modules/key-plays/view-model/KeyPlaysViewModel.js +0 -182
  128. package/dist/modules/stats/interfaces/IStats.js +0 -1
  129. package/dist/modules/stats/mocks.js +0 -686
  130. package/dist/modules/stats/utils/renderTeamStatistic.js +0 -16
  131. package/dist/modules/stats/utils.js +0 -62
  132. package/dist/modules/stats/view/ActiveAthletes/ActiveAthletes.js +0 -78
  133. package/dist/modules/stats/view/ActiveAthletes/CollapsedAthletes/CollapsedAthletes.js +0 -27
  134. package/dist/modules/stats/view/ActiveAthletes/ExpandedAthletes/ExpandedAthletes.js +0 -15
  135. package/dist/modules/stats/view/ActiveAthletes/index.js +0 -2
  136. package/dist/modules/stats/view/GameLeaders/GameLeaders.js +0 -9
  137. package/dist/modules/stats/view/GameLeaders/index.js +0 -2
  138. package/dist/modules/stats/view/MatchPredictor/DonutChart/DonutChart.js +0 -55
  139. package/dist/modules/stats/view/MatchPredictor/DonutChart/index.js +0 -1
  140. package/dist/modules/stats/view/MatchPredictor/MatchPredictorView.js +0 -66
  141. package/dist/modules/stats/view/MatchPredictor/index.js +0 -2
  142. package/dist/modules/stats/view/RecentGames/RecentGames.js +0 -22
  143. package/dist/modules/stats/view/RecentGames/index.js +0 -2
  144. package/dist/modules/stats/view/StatsQRCode/StatsQRCodeView.js +0 -38
  145. package/dist/modules/stats/view/StatsQRCode/index.js +0 -1
  146. package/dist/modules/stats/view/StatsView.js +0 -107
  147. package/dist/modules/stats/view/TeamStats/TeamStatsView.js +0 -69
  148. package/dist/modules/stats/view/TeamStats/index.js +0 -1
  149. package/dist/modules/stats/view/Teams/StatsCarousel.js +0 -14
  150. package/dist/modules/stats/view/Teams/StatsProgressBar.js +0 -18
  151. package/dist/modules/stats/view/Teams/Teams.js +0 -42
  152. package/dist/modules/stats/view/Teams/index.js +0 -2
  153. package/dist/modules/stats/view/WinProbability/WinProbability.js +0 -27
  154. package/dist/modules/stats/view/WinProbability/index.js +0 -2
  155. package/dist/modules/stats/view/components/AthletesStatistics/AthletesStatistics.js +0 -29
  156. package/dist/modules/stats/view/components/AthletesStatistics/StatisticTable.js +0 -12
  157. package/dist/modules/stats/view/components/AthletesStatistics/index.js +0 -2
  158. package/dist/modules/stats/view/index.js +0 -2
  159. package/dist/modules/stats/view-model/StatsViewModel.js +0 -90
  160. package/dist/services/BetsService.js +0 -102
  161. package/dist/services/NetworkManager/NetworkManager.js +0 -410
  162. package/dist/services/NetworkManager/errors.js +0 -45
  163. package/dist/services/StatsService.js +0 -68
  164. package/dist/services/ThemeManager/constants.js +0 -47
  165. package/dist/test-polyfills.js +0 -15
  166. package/dist/types/OverlayTypes.js +0 -4
  167. package/dist/view-models/MaestroEventViewModel.js +0 -168
  168. package/dist/view-models/OverlayViewModel.js +0 -222
  169. package/dist/view-models/PanelManagerViewModel.js +0 -80
  170. package/dist/view-models/ViewModel.js +0 -32
  171. package/dist/view-models/index.js +0 -59
@@ -1,864 +0,0 @@
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],
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 isFocused = focusKey === this.focusKey;
672
- /**
673
- * If the component was stored as lastFocusedChild, clear lastFocusedChildKey from parent
674
- */
675
- parentComponent &&
676
- parentComponent.lastFocusedChildKey === focusKey &&
677
- (parentComponent.lastFocusedChildKey = null);
678
- if (this.nativeMode) {
679
- return;
680
- }
681
- /**
682
- * If the component was also focused at this time, focus another one
683
- */
684
- if (isFocused && parentComponent && parentComponent.autoRestoreFocus) {
685
- this.setFocus(parentFocusKey);
686
- }
687
- }
688
- }
689
- getNodeLayoutByFocusKey(focusKey) {
690
- const component = this.focusableComponents[focusKey];
691
- if (component) {
692
- this.updateLayout(component.focusKey);
693
- return component.layout;
694
- }
695
- return null;
696
- }
697
- setCurrentFocusedKey(newFocusKey, details) {
698
- if (this.isFocusableComponent(this.focusKey) &&
699
- newFocusKey !== this.focusKey) {
700
- const oldComponent = this.focusableComponents[this.focusKey];
701
- const parentComponent = this.focusableComponents[oldComponent.parentFocusKey];
702
- this.saveLastFocusedChildKey(parentComponent, this.focusKey);
703
- oldComponent.onUpdateFocus?.(false);
704
- oldComponent.onBecameBlurredHandler?.(this.getNodeLayoutByFocusKey(this.focusKey), details);
705
- }
706
- this.focusKey = newFocusKey;
707
- if (this.isFocusableComponent(this.focusKey)) {
708
- const newComponent = this.focusableComponents[this.focusKey];
709
- newComponent.onUpdateFocus?.(true);
710
- newComponent.onBecameFocusedHandler?.(this.getNodeLayoutByFocusKey(this.focusKey), details);
711
- }
712
- }
713
- updateParentsHasFocusedChild(focusKey, details) {
714
- const parents = [];
715
- let currentComponent = this.focusableComponents[focusKey];
716
- /**
717
- * Recursively iterate the tree up and find all the parents' focus keys
718
- */
719
- while (currentComponent) {
720
- const { parentFocusKey } = currentComponent;
721
- const parentComponent = this.focusableComponents[parentFocusKey];
722
- if (parentComponent) {
723
- const { focusKey: currentParentFocusKey } = parentComponent;
724
- parents.push(currentParentFocusKey);
725
- }
726
- currentComponent = parentComponent;
727
- }
728
- const parentsToRemoveFlag = difference(this.parentsHavingFocusedChild, parents);
729
- const parentsToAddFlag = difference(parents, this.parentsHavingFocusedChild);
730
- forEach(parentsToRemoveFlag, (parentFocusKey) => {
731
- const parentComponent = this.focusableComponents[parentFocusKey];
732
- parentComponent &&
733
- parentComponent.trackChildren &&
734
- parentComponent.onUpdateHasFocusedChild?.(false);
735
- this.onIntermediateNodeBecameBlurred(parentFocusKey, details);
736
- });
737
- forEach(parentsToAddFlag, (parentFocusKey) => {
738
- const parentComponent = this.focusableComponents[parentFocusKey];
739
- parentComponent &&
740
- parentComponent.trackChildren &&
741
- parentComponent.onUpdateHasFocusedChild?.(true);
742
- this.onIntermediateNodeBecameFocused(parentFocusKey, details);
743
- });
744
- this.parentsHavingFocusedChild = parents;
745
- }
746
- updateParentsLastFocusedChild(focusKey) {
747
- let currentComponent = this.focusableComponents[focusKey];
748
- /**
749
- * Recursively iterate the tree up and update all the parent's lastFocusedChild
750
- */
751
- while (currentComponent) {
752
- const { parentFocusKey } = currentComponent;
753
- const parentComponent = this.focusableComponents[parentFocusKey];
754
- if (parentComponent) {
755
- this.saveLastFocusedChildKey(parentComponent, currentComponent.focusKey);
756
- }
757
- currentComponent = parentComponent;
758
- }
759
- }
760
- getKeyMap() {
761
- return this.keyMap;
762
- }
763
- setKeyMap(keyMap) {
764
- this.keyMap = {
765
- ...this.getKeyMap(),
766
- ...keyMap,
767
- };
768
- }
769
- isFocusableComponent(focusKey) {
770
- return !!this.focusableComponents[focusKey];
771
- }
772
- /**
773
- * Checks whether the focusableComponent is actually participating in spatial navigation (in other words, is a
774
- * 'focusable' focusableComponent). Seems less confusing than calling it isFocusableFocusableComponent()
775
- */
776
- isParticipatingFocusableComponent(focusKey) {
777
- return (this.isFocusableComponent(focusKey) &&
778
- this.focusableComponents[focusKey].focusable);
779
- }
780
- onIntermediateNodeBecameFocused(focusKey, details) {
781
- this.isParticipatingFocusableComponent(focusKey) &&
782
- this.focusableComponents[focusKey].onBecameFocusedHandler?.(this.getNodeLayoutByFocusKey(focusKey), details);
783
- }
784
- onIntermediateNodeBecameBlurred(focusKey, details) {
785
- this.isParticipatingFocusableComponent(focusKey) &&
786
- this.focusableComponents[focusKey].onBecameBlurredHandler?.(this.getNodeLayoutByFocusKey(focusKey), details);
787
- }
788
- pause() {
789
- this.focusKey = null;
790
- this.parentsHavingFocusedChild = [];
791
- this.pressedKeys = {};
792
- this.log('pause', 'paused');
793
- this.visualDebugger?.clear();
794
- this.visualDebugger?.clearLayouts();
795
- this.getFocusableComponents().forEach((component) => {
796
- component.onUpdateFocus?.(false);
797
- });
798
- this.paused = true;
799
- }
800
- resume() {
801
- this.paused = false;
802
- }
803
- setFocus(targetFocusKey, details = {}) {
804
- const lastFocusedKey = this.focusKey;
805
- const newFocusKey = this.getNextFocusKey(targetFocusKey);
806
- this.log('setFocus', 'newFocusKey', newFocusKey);
807
- this.setCurrentFocusedKey(newFocusKey, details);
808
- this.updateParentsHasFocusedChild(newFocusKey, details);
809
- this.updateParentsLastFocusedChild(lastFocusedKey);
810
- }
811
- updateAllLayouts() {
812
- if (this.nativeMode) {
813
- return;
814
- }
815
- Object.entries(this.focusableComponents).forEach(([focusKey]) => {
816
- this.updateLayout(focusKey);
817
- });
818
- }
819
- updateLayout(focusKey) {
820
- const component = this.focusableComponents[focusKey];
821
- if (!component || this.nativeMode || component.layoutUpdated) {
822
- return;
823
- }
824
- const { node } = component;
825
- measureLayout(node, (x, y, width, height, left, top) => {
826
- component.layout = {
827
- x,
828
- y,
829
- width,
830
- height,
831
- left,
832
- top,
833
- node,
834
- };
835
- });
836
- }
837
- updateFocusable(focusKey, { node, preferredChildFocusKey, focusable, blockNavigationOut, }) {
838
- if (this.nativeMode) {
839
- return;
840
- }
841
- const component = this.focusableComponents[focusKey];
842
- if (component) {
843
- component.preferredChildFocusKey = preferredChildFocusKey;
844
- component.focusable = focusable ?? component.focusable;
845
- component.blockNavigationOut = blockNavigationOut;
846
- if (node) {
847
- component.node = node;
848
- }
849
- }
850
- }
851
- isNativeMode() {
852
- return this.nativeMode;
853
- }
854
- getFocusableComponents() {
855
- return Object.values(this.focusableComponents);
856
- }
857
- getFocusableComponentsByParentFocusKey(parentFocusKey) {
858
- return Object.values(this.focusableComponents).filter((component) => component.parentFocusKey === parentFocusKey);
859
- }
860
- }
861
- /**
862
- * Export singleton
863
- */
864
- export default SpatialNavigation;