@dr.pogodin/react-utils 1.25.6 → 1.26.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 (290) hide show
  1. package/babel.config.js +3 -1
  2. package/bin/build.js +18 -4
  3. package/build/development/client/getInj.js +9 -8
  4. package/build/development/client/getInj.js.map +1 -1
  5. package/build/development/client/index.js +6 -3
  6. package/build/development/client/index.js.map +1 -1
  7. package/build/development/client/init.js +4 -0
  8. package/build/development/client/init.js.map +1 -1
  9. package/build/development/index.js +166 -38
  10. package/build/development/index.js.map +1 -1
  11. package/build/development/server/Cache.js +13 -13
  12. package/build/development/server/Cache.js.map +1 -1
  13. package/build/development/server/index.js +16 -23
  14. package/build/development/server/index.js.map +1 -1
  15. package/build/development/server/renderer.js +56 -43
  16. package/build/development/server/renderer.js.map +1 -1
  17. package/build/development/server/server.js +9 -5
  18. package/build/development/server/server.js.map +1 -1
  19. package/build/development/server/utils/errors.js +12 -9
  20. package/build/development/server/utils/errors.js.map +1 -1
  21. package/build/development/server/utils/index.js +2 -2
  22. package/build/development/server/utils/index.js.map +1 -1
  23. package/build/development/shared/components/Button/index.js +7 -5
  24. package/build/development/shared/components/Button/index.js.map +1 -1
  25. package/build/development/shared/components/Checkbox/index.js +24 -26
  26. package/build/development/shared/components/Checkbox/index.js.map +1 -1
  27. package/build/development/shared/components/Dropdown/index.js +24 -17
  28. package/build/development/shared/components/Dropdown/index.js.map +1 -1
  29. package/build/development/shared/components/GenericLink/index.js +44 -37
  30. package/build/development/shared/components/GenericLink/index.js.map +1 -1
  31. package/build/development/shared/components/Input/index.js +7 -7
  32. package/build/development/shared/components/Input/index.js.map +1 -1
  33. package/build/development/shared/components/Link.js +10 -9
  34. package/build/development/shared/components/Link.js.map +1 -1
  35. package/build/development/shared/components/MetaTags.js +22 -18
  36. package/build/development/shared/components/MetaTags.js.map +1 -1
  37. package/build/development/shared/components/Modal/index.js +16 -16
  38. package/build/development/shared/components/Modal/index.js.map +1 -1
  39. package/build/development/shared/components/NavLink.js +10 -9
  40. package/build/development/shared/components/NavLink.js.map +1 -1
  41. package/build/development/shared/components/PageLayout/index.js +16 -18
  42. package/build/development/shared/components/PageLayout/index.js.map +1 -1
  43. package/build/development/shared/components/ScalableRect/index.js +27 -7
  44. package/build/development/shared/components/ScalableRect/index.js.map +1 -1
  45. package/build/development/shared/components/Throbber/index.js +14 -22
  46. package/build/development/shared/components/Throbber/index.js.map +1 -1
  47. package/build/development/shared/components/WithTooltip/Tooltip.js +19 -21
  48. package/build/development/shared/components/WithTooltip/Tooltip.js.map +1 -1
  49. package/build/development/shared/components/WithTooltip/index.js +11 -10
  50. package/build/development/shared/components/WithTooltip/index.js.map +1 -1
  51. package/build/development/shared/components/YouTubeVideo/index.js +15 -13
  52. package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
  53. package/build/development/shared/components/index.js +2 -2
  54. package/build/development/shared/components/index.js.map +1 -1
  55. package/build/development/shared/utils/config.js.map +1 -1
  56. package/build/development/shared/utils/globalState.js +15 -0
  57. package/build/development/shared/utils/globalState.js.map +1 -0
  58. package/build/development/shared/utils/index.js +13 -10
  59. package/build/development/shared/utils/index.js.map +1 -1
  60. package/build/development/shared/utils/isomorphy/buildInfo.js +7 -3
  61. package/build/development/shared/utils/isomorphy/buildInfo.js.map +1 -1
  62. package/build/development/shared/utils/isomorphy/environment-check.js.map +1 -1
  63. package/build/development/shared/utils/isomorphy/index.js.map +1 -1
  64. package/build/development/shared/utils/jest/E2eSsrEnv.js +35 -28
  65. package/build/development/shared/utils/jest/E2eSsrEnv.js.map +1 -1
  66. package/build/development/shared/utils/jest/global.js +17 -0
  67. package/build/development/shared/utils/jest/global.js.map +1 -0
  68. package/build/development/shared/utils/jest/index.js +17 -9
  69. package/build/development/shared/utils/jest/index.js.map +1 -1
  70. package/build/development/shared/utils/splitComponent.js +25 -34
  71. package/build/development/shared/utils/splitComponent.js.map +1 -1
  72. package/build/development/shared/utils/time.js +16 -12
  73. package/build/development/shared/utils/time.js.map +1 -1
  74. package/build/development/shared/utils/webpack.js +3 -3
  75. package/build/development/shared/utils/webpack.js.map +1 -1
  76. package/build/development/web.bundle.js +92 -82
  77. package/build/production/client/getInj.js +1 -1
  78. package/build/production/client/getInj.js.map +1 -1
  79. package/build/production/client/index.js +4 -4
  80. package/build/production/client/index.js.map +1 -1
  81. package/build/production/client/init.js +3 -1
  82. package/build/production/client/init.js.map +1 -1
  83. package/build/production/index.js +1 -1
  84. package/build/production/index.js.map +1 -1
  85. package/build/production/server/Cache.js +7 -8
  86. package/build/production/server/Cache.js.map +1 -1
  87. package/build/production/server/index.js +5 -4
  88. package/build/production/server/index.js.map +1 -1
  89. package/build/production/server/renderer.js +32 -30
  90. package/build/production/server/renderer.js.map +1 -1
  91. package/build/production/server/server.js +7 -5
  92. package/build/production/server/server.js.map +1 -1
  93. package/build/production/server/utils/errors.js +9 -10
  94. package/build/production/server/utils/errors.js.map +1 -1
  95. package/build/production/server/utils/index.js +1 -1
  96. package/build/production/server/utils/index.js.map +1 -1
  97. package/build/production/shared/components/Button/index.js +3 -3
  98. package/build/production/shared/components/Button/index.js.map +1 -1
  99. package/build/production/shared/components/Checkbox/index.js +11 -11
  100. package/build/production/shared/components/Checkbox/index.js.map +1 -1
  101. package/build/production/shared/components/Dropdown/index.js +11 -11
  102. package/build/production/shared/components/Dropdown/index.js.map +1 -1
  103. package/build/production/shared/components/GenericLink/index.js +25 -20
  104. package/build/production/shared/components/GenericLink/index.js.map +1 -1
  105. package/build/production/shared/components/Input/index.js +7 -7
  106. package/build/production/shared/components/Input/index.js.map +1 -1
  107. package/build/production/shared/components/Link.js +2 -2
  108. package/build/production/shared/components/Link.js.map +1 -1
  109. package/build/production/shared/components/MetaTags.js +10 -10
  110. package/build/production/shared/components/MetaTags.js.map +1 -1
  111. package/build/production/shared/components/Modal/index.js +2 -2
  112. package/build/production/shared/components/Modal/index.js.map +1 -1
  113. package/build/production/shared/components/NavLink.js +1 -1
  114. package/build/production/shared/components/NavLink.js.map +1 -1
  115. package/build/production/shared/components/PageLayout/index.js +2 -2
  116. package/build/production/shared/components/PageLayout/index.js.map +1 -1
  117. package/build/production/shared/components/ScalableRect/index.js +7 -3
  118. package/build/production/shared/components/ScalableRect/index.js.map +1 -1
  119. package/build/production/shared/components/Throbber/index.js +2 -2
  120. package/build/production/shared/components/Throbber/index.js.map +1 -1
  121. package/build/production/shared/components/WithTooltip/Tooltip.js +13 -13
  122. package/build/production/shared/components/WithTooltip/Tooltip.js.map +1 -1
  123. package/build/production/shared/components/WithTooltip/index.js +3 -3
  124. package/build/production/shared/components/WithTooltip/index.js.map +1 -1
  125. package/build/production/shared/components/YouTubeVideo/index.js +7 -7
  126. package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
  127. package/build/production/shared/components/index.js +1 -1
  128. package/build/production/shared/components/index.js.map +1 -1
  129. package/build/production/shared/utils/config.js.map +1 -1
  130. package/build/production/shared/utils/globalState.js +3 -0
  131. package/build/production/shared/utils/globalState.js.map +1 -0
  132. package/build/production/shared/utils/index.js +2 -2
  133. package/build/production/shared/utils/index.js.map +1 -1
  134. package/build/production/shared/utils/isomorphy/buildInfo.js +7 -4
  135. package/build/production/shared/utils/isomorphy/buildInfo.js.map +1 -1
  136. package/build/production/shared/utils/isomorphy/environment-check.js.map +1 -1
  137. package/build/production/shared/utils/isomorphy/index.js.map +1 -1
  138. package/build/production/shared/utils/jest/E2eSsrEnv.js +9 -9
  139. package/build/production/shared/utils/jest/E2eSsrEnv.js.map +1 -1
  140. package/build/production/shared/utils/jest/global.js +2 -0
  141. package/build/production/shared/utils/jest/global.js.map +1 -0
  142. package/build/production/shared/utils/jest/index.js +5 -5
  143. package/build/production/shared/utils/jest/index.js.map +1 -1
  144. package/build/production/shared/utils/splitComponent.js +16 -16
  145. package/build/production/shared/utils/splitComponent.js.map +1 -1
  146. package/build/production/shared/utils/time.js +12 -8
  147. package/build/production/shared/utils/time.js.map +1 -1
  148. package/build/production/shared/utils/webpack.js +3 -3
  149. package/build/production/shared/utils/webpack.js.map +1 -1
  150. package/build/production/web.bundle.js +1 -1
  151. package/build/production/web.bundle.js.map +1 -1
  152. package/build/types-code/client/getInj.d.ts +3 -0
  153. package/build/types-code/client/index.d.ts +11 -0
  154. package/build/types-code/client/init.d.ts +9 -0
  155. package/build/types-code/index.d.ts +10 -0
  156. package/build/types-code/server/Cache.d.ts +37 -0
  157. package/build/types-code/server/index.d.ts +145 -0
  158. package/build/types-code/server/renderer.d.ts +106 -0
  159. package/build/types-code/server/server.d.ts +41 -0
  160. package/build/types-code/server/utils/errors.d.ts +104 -0
  161. package/build/types-code/server/utils/index.d.ts +1 -0
  162. package/build/types-code/shared/components/Button/index.d.ts +27 -0
  163. package/build/types-code/shared/components/Checkbox/index.d.ts +21 -0
  164. package/build/types-code/shared/components/Dropdown/index.d.ts +23 -0
  165. package/build/types-code/shared/components/GenericLink/index.d.ts +61 -0
  166. package/build/types-code/shared/components/Input/index.d.ts +11 -0
  167. package/build/types-code/shared/components/Link.d.ts +12 -0
  168. package/build/types-code/shared/components/MetaTags.d.ts +68 -0
  169. package/build/types-code/shared/components/Modal/index.d.ts +26 -0
  170. package/build/types-code/shared/components/NavLink.d.ts +5 -0
  171. package/build/types-code/shared/components/PageLayout/index.d.ts +16 -0
  172. package/build/types-code/shared/components/ScalableRect/index.d.ts +19 -0
  173. package/build/types-code/shared/components/Throbber/index.d.ts +9 -0
  174. package/build/types-code/shared/components/WithTooltip/Tooltip.d.ts +23 -0
  175. package/build/types-code/shared/components/WithTooltip/index.d.ts +17 -0
  176. package/build/types-code/shared/components/YouTubeVideo/index.d.ts +13 -0
  177. package/build/types-code/shared/components/index.d.ts +16 -0
  178. package/build/types-code/shared/utils/config.d.ts +2 -0
  179. package/build/types-code/shared/utils/globalState.d.ts +20 -0
  180. package/build/types-code/shared/utils/index.d.ts +52 -0
  181. package/build/types-code/shared/utils/isomorphy/buildInfo.d.ts +23 -0
  182. package/build/types-code/shared/utils/isomorphy/environment-check.d.ts +11 -0
  183. package/build/types-code/shared/utils/isomorphy/index.d.ts +20 -0
  184. package/build/types-code/shared/utils/jest/E2eSsrEnv.d.ts +31 -0
  185. package/build/types-code/shared/utils/jest/global.d.ts +12 -0
  186. package/build/types-code/shared/utils/jest/index.d.ts +85 -0
  187. package/build/types-code/shared/utils/splitComponent.d.ts +41 -0
  188. package/build/types-code/shared/utils/time.d.ts +62 -0
  189. package/build/types-code/shared/utils/webpack.d.ts +18 -0
  190. package/build/types-scss/__tests__/js/config/publicPath support/__assets__/style.scss.d.ts +1 -0
  191. package/build/types-scss/__tests__/js/config/stylename-generation/__assets__/MockPackageA/TestComponent/style.scss.d.ts +1 -0
  192. package/build/types-scss/__tests__/js/config/stylename-generation/__assets__/MockPackageB/TestComponent/style.scss.d.ts +1 -0
  193. package/build/types-scss/__tests__/js/config/stylename-generation/__assets__/style.scss.d.ts +1 -0
  194. package/build/types-scss/__tests__/js/shared/components/NavLink/styles.scss.d.ts +1 -0
  195. package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/ComponentA/style.scss.d.ts +1 -0
  196. package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/ComponentB/style.scss.d.ts +1 -0
  197. package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/ComponentC/style.scss.d.ts +1 -0
  198. package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/style.scss.d.ts +1 -0
  199. package/build/types-scss/__tests__/ts/config/publicPath support/__assets__/style.scss.d.ts +1 -0
  200. package/build/types-scss/__tests__/ts/config/stylename-generation/__assets__/MockPackageA/TestComponent/style.scss.d.ts +1 -0
  201. package/build/types-scss/__tests__/ts/config/stylename-generation/__assets__/MockPackageB/TestComponent/style.scss.d.ts +1 -0
  202. package/build/types-scss/__tests__/ts/config/stylename-generation/__assets__/style.scss.d.ts +1 -0
  203. package/build/types-scss/__tests__/ts/shared/components/NavLink/styles.scss.d.ts +1 -0
  204. package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/ComponentA/style.scss.d.ts +1 -0
  205. package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/ComponentB/style.scss.d.ts +1 -0
  206. package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/ComponentC/style.scss.d.ts +1 -0
  207. package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/style.scss.d.ts +1 -0
  208. package/build/types-scss/src/shared/components/Button/style.scss.d.ts +6 -0
  209. package/build/types-scss/src/shared/components/Checkbox/theme.scss.d.ts +6 -0
  210. package/build/types-scss/src/shared/components/Dropdown/theme.scss.d.ts +9 -0
  211. package/build/types-scss/src/shared/components/GenericLink/style.scss.d.ts +1 -0
  212. package/build/types-scss/src/shared/components/Input/theme.scss.d.ts +6 -0
  213. package/build/types-scss/src/shared/components/Modal/base-theme.scss.d.ts +5 -0
  214. package/build/types-scss/src/shared/components/PageLayout/base-theme.scss.d.ts +6 -0
  215. package/build/types-scss/src/shared/components/ScalableRect/style.scss.d.ts +2 -0
  216. package/build/types-scss/src/shared/components/Throbber/theme.scss.d.ts +6 -0
  217. package/build/types-scss/src/shared/components/WithTooltip/default-theme.scss.d.ts +7 -0
  218. package/build/types-scss/src/shared/components/YouTubeVideo/base.scss.d.ts +5 -0
  219. package/build/types-scss/src/shared/components/YouTubeVideo/throbber.scss.d.ts +4 -0
  220. package/config/eslint/jest.json +3 -2
  221. package/config/eslint/typescript.js +34 -0
  222. package/config/jest/default.js +3 -3
  223. package/package.json +74 -32
  224. package/src/client/getInj.ts +43 -0
  225. package/src/client/index.tsx +40 -0
  226. package/src/client/init.ts +47 -0
  227. package/src/index.ts +58 -0
  228. package/src/server/Cache.ts +68 -0
  229. package/src/server/index.ts +230 -0
  230. package/src/server/renderer.tsx +604 -0
  231. package/src/server/server.ts +309 -0
  232. package/src/server/utils/errors.ts +135 -0
  233. package/src/server/utils/index.ts +3 -0
  234. package/src/shared/components/Button/index.tsx +146 -0
  235. package/src/shared/components/Button/style.scss +53 -0
  236. package/src/shared/components/Checkbox/index.tsx +71 -0
  237. package/src/shared/components/Checkbox/theme.scss +43 -0
  238. package/src/shared/components/Dropdown/index.tsx +144 -0
  239. package/src/shared/components/Dropdown/theme.scss +63 -0
  240. package/src/shared/components/GenericLink/index.tsx +157 -0
  241. package/src/shared/components/GenericLink/style.scss +3 -0
  242. package/src/shared/components/Input/index.tsx +59 -0
  243. package/src/shared/components/Input/theme.scss +27 -0
  244. package/src/shared/components/Link.tsx +21 -0
  245. package/src/shared/components/MetaTags.tsx +170 -0
  246. package/src/shared/components/Modal/base-theme.scss +38 -0
  247. package/src/shared/components/Modal/index.tsx +144 -0
  248. package/src/shared/components/Modal/styles.scss +5 -0
  249. package/src/shared/components/NavLink.tsx +13 -0
  250. package/src/shared/components/PageLayout/base-theme.scss +30 -0
  251. package/src/shared/components/PageLayout/index.tsx +76 -0
  252. package/src/shared/components/ScalableRect/index.tsx +84 -0
  253. package/src/shared/components/ScalableRect/style.scss +10 -0
  254. package/src/shared/components/Throbber/index.tsx +43 -0
  255. package/src/shared/components/Throbber/theme.scss +26 -0
  256. package/src/shared/components/WithTooltip/Tooltip.tsx +353 -0
  257. package/src/shared/components/WithTooltip/default-theme.scss +36 -0
  258. package/src/shared/components/WithTooltip/index.tsx +204 -0
  259. package/src/shared/components/YouTubeVideo/base.scss +13 -0
  260. package/src/shared/components/YouTubeVideo/index.tsx +96 -0
  261. package/src/shared/components/YouTubeVideo/throbber.scss +11 -0
  262. package/src/shared/components/index.ts +17 -0
  263. package/src/shared/utils/config.ts +21 -0
  264. package/src/shared/utils/globalState.ts +29 -0
  265. package/src/shared/utils/index.ts +105 -0
  266. package/src/shared/utils/isomorphy/buildInfo.ts +54 -0
  267. package/src/shared/utils/isomorphy/environment-check.ts +18 -0
  268. package/src/shared/utils/isomorphy/index.ts +38 -0
  269. package/src/shared/utils/jest/E2eSsrEnv.ts +250 -0
  270. package/src/shared/utils/jest/global.ts +19 -0
  271. package/src/shared/utils/jest/index.tsx +157 -0
  272. package/src/shared/utils/splitComponent.tsx +255 -0
  273. package/src/shared/utils/time.ts +118 -0
  274. package/src/shared/utils/webpack.ts +45 -0
  275. package/src/styles/_global/reset.css +52 -0
  276. package/src/styles/global.scss +11 -0
  277. package/tsconfig.configs.json +18 -0
  278. package/tsconfig.json +27 -0
  279. package/tsconfig.types.json +53 -0
  280. package/typed-scss-modules.config.ts +9 -0
  281. package/types.d.ts +37 -0
  282. package/{webpack.config.js → webpack.config.ts} +7 -3
  283. package/config/babel/node-ssr.js +0 -85
  284. package/config/babel/webpack.js +0 -123
  285. package/config/webpack/app-base.js +0 -330
  286. package/config/webpack/app-development.js +0 -80
  287. package/config/webpack/app-production.js +0 -60
  288. package/config/webpack/lib-base.js +0 -155
  289. package/config/webpack/lib-development.js +0 -45
  290. package/config/webpack/lib-production.js +0 -44
@@ -0,0 +1,353 @@
1
+ /**
2
+ * The actual tooltip component. It is rendered outside the regular document
3
+ * hierarchy, and with sub-components managed without React to achieve the best
4
+ * performance during animation.
5
+ */
6
+ /* global document, window */
7
+
8
+ import {
9
+ type ReactNode,
10
+ forwardRef,
11
+ useEffect,
12
+ useImperativeHandle,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
16
+
17
+ import { createPortal } from 'react-dom';
18
+
19
+ import PT from 'prop-types';
20
+
21
+ /* Valid placements of the rendered tooltip. They will be overriden when
22
+ * necessary to fit the tooltip within the viewport. */
23
+ export enum PLACEMENTS {
24
+ ABOVE_CURSOR = 'ABOVE_CURSOR',
25
+ ABOVE_ELEMENT = 'ABOVE_ELEMENT',
26
+ BELOW_CURSOR = 'BELOW_CURSOR',
27
+ BELOW_ELEMENT = 'BELOW_ELEMENT',
28
+ }
29
+
30
+ const ARROW_STYLE_DOWN = [
31
+ 'border-bottom-color:transparent',
32
+ 'border-left-color:transparent',
33
+ 'border-right-color:transparent',
34
+ ].join(';');
35
+
36
+ const ARROW_STYLE_UP = [
37
+ 'border-top-color:transparent',
38
+ 'border-left-color:transparent',
39
+ 'border-right-color:transparent',
40
+ ].join(';');
41
+
42
+ type ComponentsT = {
43
+ container: HTMLDivElement;
44
+ arrow: HTMLDivElement;
45
+ content: HTMLDivElement;
46
+ };
47
+
48
+ type HeapT = {
49
+ lastElement?: HTMLElement;
50
+ lastPageX: number;
51
+ lastPageY: number;
52
+ lastPlacement?: PLACEMENTS | undefined;
53
+ };
54
+
55
+ export interface TooltipThemeT {
56
+ appearance?: string;
57
+ arrow?: string;
58
+ content?: string;
59
+ container?: string;
60
+ }
61
+
62
+ /**
63
+ * Creates tooltip components.
64
+ * @ignore
65
+ * @param {object} theme Themes to use for tooltip container, arrow,
66
+ * and content.
67
+ * @return {object} Object with DOM references to the container components:
68
+ * container, arrow, content.
69
+ */
70
+ function createTooltipComponents(theme: TooltipThemeT): ComponentsT {
71
+ const arrow = document.createElement('div');
72
+ if (theme.arrow) arrow.setAttribute('class', theme.arrow);
73
+
74
+ const content = document.createElement('div');
75
+ if (theme.content) content.setAttribute('class', theme.content);
76
+
77
+ const container = document.createElement('div');
78
+ if (theme.container) container.setAttribute('class', theme.container);
79
+
80
+ container.appendChild(arrow);
81
+ container.appendChild(content);
82
+ document.body.appendChild(container);
83
+
84
+ return { container, arrow, content };
85
+ }
86
+
87
+ type TooltipRectsT = {
88
+ arrow: DOMRect;
89
+ container: DOMRect;
90
+ };
91
+
92
+ /**
93
+ * Generates bounding client rectangles for tooltip components.
94
+ * @ignore
95
+ * @param tooltip DOM references to the tooltip components.
96
+ * @param tooltip.arrow
97
+ * @param tooltip.container
98
+ * @return Object holding tooltip rectangles in
99
+ * two fields.
100
+ */
101
+ function calcTooltipRects(tooltip: ComponentsT): TooltipRectsT {
102
+ return {
103
+ arrow: tooltip.arrow.getBoundingClientRect(),
104
+ container: tooltip.container.getBoundingClientRect(),
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Calculates the document viewport size.
110
+ * @ignore
111
+ * @return {{x, y, width, height}}
112
+ */
113
+ function calcViewportRect() {
114
+ const { scrollX, scrollY } = window;
115
+ const { documentElement: { clientHeight, clientWidth } } = document;
116
+ return {
117
+ left: scrollX,
118
+ right: scrollX + clientWidth,
119
+ top: scrollY,
120
+ bottom: scrollY + clientHeight,
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Calculates tooltip and arrow positions for the placement just above
126
+ * the cursor.
127
+ * @ignore
128
+ * @param {number} x Cursor page-x position.
129
+ * @param {number} y Cursor page-y position.
130
+ * @param {object} tooltipRects Bounding client rectangles of tooltip parts.
131
+ * @param {object} tooltipRects.arrow
132
+ * @param {object} tooltipRects.container
133
+ * @return {object} Contains the following fields:
134
+ * - {number} arrowX
135
+ * - {number} arrowY
136
+ * - {number} containerX
137
+ * - {number} containerY
138
+ * - {string} baseArrowStyle
139
+ */
140
+ function calcPositionAboveXY(
141
+ x: number,
142
+ y: number,
143
+ tooltipRects: TooltipRectsT,
144
+ ) {
145
+ const { arrow, container } = tooltipRects;
146
+ return {
147
+ arrowX: 0.5 * (container.width - arrow.width),
148
+ arrowY: container.height,
149
+ containerX: x - container.width / 2,
150
+ containerY: y - container.height - arrow.height / 1.5,
151
+
152
+ // TODO: Instead of already setting the base style here, we should
153
+ // introduce a set of constants for arrow directions, which will help
154
+ // to do checks dependant on the arrow direction.
155
+ baseArrowStyle: ARROW_STYLE_DOWN,
156
+ };
157
+ }
158
+
159
+ /*
160
+ const HIT = {
161
+ NONE: false,
162
+ LEFT: 'LEFT',
163
+ RIGHT: 'RIGHT',
164
+ TOP: 'TOP',
165
+ BOTTOM: 'BOTTOM',
166
+ };
167
+ */
168
+
169
+ /**
170
+ * Checks whether
171
+ * @param {object} pos
172
+ * @param {object} tooltipRects
173
+ * @param {object} viewportRect
174
+ * @return {HIT}
175
+ */
176
+ /*
177
+ function checkViewportFit(pos, tooltipRects, viewportRect) {
178
+ const { containerX, containerY } = pos;
179
+ if (containerX < viewportRect.left + 6) return HIT.LEFT;
180
+ if (containerX > viewportRect.right - 6) return HIT.RIGHT;
181
+ return HIT.NONE;
182
+ }
183
+ */
184
+
185
+ /**
186
+ * Shifts tooltip horizontally to fit into the viewport, while keeping
187
+ * the arrow pointed to the XY point.
188
+ * @param {number} x
189
+ * @param {number} y
190
+ * @param {object} pos
191
+ * @param {number} pageXOffset
192
+ * @param {number} pageXWidth
193
+ */
194
+ /*
195
+ function xPageFitCorrection(x, y, pos, pageXOffset, pageXWidth) {
196
+ if (pos.containerX < pageXOffset + 6) {
197
+ pos.containerX = pageXOffset + 6;
198
+ pos.arrowX = Math.max(6, pageX - containerX - arrowRect.width / 2);
199
+ } else {
200
+ const maxX = pageXOffset + docRect.width - containerRect.width - 6;
201
+ if (containerX > maxX) {
202
+ containerX = maxX;
203
+ arrowX = Math.min(
204
+ containerRect.width - 6,
205
+ pageX - containerX - arrowRect.width / 2,
206
+ );
207
+ }
208
+ }
209
+ }
210
+ */
211
+
212
+ /**
213
+ * Sets positions of tooltip components to point the tooltip to the specified
214
+ * page point.
215
+ * @ignore
216
+ * @param pageX
217
+ * @param pageY
218
+ * @param placement
219
+ * @param element DOM reference to the element wrapped by the tooltip.
220
+ * @param tooltip
221
+ * @param tooltip.arrow DOM reference to the tooltip arrow.
222
+ * @param tooltip.container DOM reference to the tooltip container.
223
+ */
224
+ function setComponentPositions(
225
+ pageX: number,
226
+ pageY: number,
227
+ placement: PLACEMENTS | undefined,
228
+ element: HTMLElement | undefined,
229
+ tooltip: ComponentsT,
230
+ ) {
231
+ const tooltipRects = calcTooltipRects(tooltip);
232
+ const viewportRect = calcViewportRect();
233
+
234
+ /* Default container coords: tooltip at the top. */
235
+ const pos = calcPositionAboveXY(pageX, pageY, tooltipRects);
236
+
237
+ if (pos.containerX < viewportRect.left + 6) {
238
+ pos.containerX = viewportRect.left + 6;
239
+ pos.arrowX = Math.max(
240
+ 6,
241
+ pageX - pos.containerX - tooltipRects.arrow.width / 2,
242
+ );
243
+ } else {
244
+ const maxX = viewportRect.right - 6 - tooltipRects.container.width;
245
+ if (pos.containerX > maxX) {
246
+ pos.containerX = maxX;
247
+ pos.arrowX = Math.min(
248
+ tooltipRects.container.width - 6,
249
+ pageX - pos.containerX - tooltipRects.arrow.width / 2,
250
+ );
251
+ }
252
+ }
253
+
254
+ /* If tooltip has not enough space on top - make it bottom tooltip. */
255
+ if (pos.containerY < viewportRect.top + 6) {
256
+ pos.containerY += tooltipRects.container.height
257
+ + 2 * tooltipRects.arrow.height;
258
+ pos.arrowY -= tooltipRects.container.height
259
+ + tooltipRects.arrow.height;
260
+ pos.baseArrowStyle = ARROW_STYLE_UP;
261
+ }
262
+
263
+ const containerStyle = `left:${pos.containerX}px;top:${pos.containerY}px`;
264
+ tooltip.container.setAttribute('style', containerStyle);
265
+
266
+ const arrowStyle = `${pos.baseArrowStyle};left:${pos.arrowX}px;top:${pos.arrowY}px`;
267
+ tooltip.arrow.setAttribute('style', arrowStyle);
268
+ }
269
+
270
+ /* The Tooltip component itself. */
271
+ const Tooltip = forwardRef<unknown, {
272
+ children?: ReactNode;
273
+ theme: any;
274
+ }>(({ children, theme }, ref) => {
275
+ // NOTE: The way it has to be implemented, for clean mounting and unmounting
276
+ // at the client side, the <Tooltip> is fully mounted into DOM in the next
277
+ // rendering cycles, and only then it can be correctly measured and positioned.
278
+ // Thus, when we create the <Tooltip> we have to record its target positioning
279
+ // details, and then apply them when it is created.
280
+
281
+ const { current: heap } = useRef<HeapT>({
282
+ lastElement: undefined,
283
+ lastPageX: 0,
284
+ lastPageY: 0,
285
+ lastPlacement: undefined,
286
+ });
287
+
288
+ const [components, setComponents] = useState<ComponentsT | null>(null);
289
+
290
+ const pointTo = (
291
+ pageX: number,
292
+ pageY: number,
293
+ placement: PLACEMENTS,
294
+ element: HTMLElement,
295
+ ) => {
296
+ heap.lastElement = element;
297
+ heap.lastPageX = pageX;
298
+ heap.lastPageY = pageY;
299
+ heap.lastPlacement = placement;
300
+
301
+ if (components) {
302
+ setComponentPositions(pageX, pageY, placement, element, components);
303
+ }
304
+ };
305
+ useImperativeHandle(ref, () => ({ pointTo }));
306
+
307
+ /* Inits and destroys tooltip components. */
308
+ useEffect(() => {
309
+ const x = createTooltipComponents(theme);
310
+ setComponents(x);
311
+ return () => {
312
+ document.body.removeChild(x.container);
313
+ setComponents(null);
314
+ };
315
+ }, [theme]);
316
+
317
+ useEffect(() => {
318
+ if (components) {
319
+ setComponentPositions(
320
+ heap.lastPageX,
321
+ heap.lastPageY,
322
+ heap.lastPlacement,
323
+ heap.lastElement,
324
+ components,
325
+ );
326
+ }
327
+ }, [
328
+ components,
329
+ // BEWARE: Careful about these dependencies - they are updated when mouse
330
+ // is moved over the tool-tipped element, thus potentially may cause
331
+ // unnecessary firing of this effect on each mouse event. It does not
332
+ // happen now just because the mouse movements themselves are not causing
333
+ // the component re-rendering, thus dependencies of this effect are not
334
+ // really re-evaluated.
335
+ heap.lastPageX,
336
+ heap.lastPageY,
337
+ heap.lastPlacement,
338
+ heap.lastElement,
339
+ ]);
340
+
341
+ return components ? createPortal(children, components.content) : null;
342
+ });
343
+
344
+ Tooltip.propTypes = {
345
+ children: PT.node,
346
+ theme: PT.shape({}).isRequired,
347
+ };
348
+
349
+ Tooltip.defaultProps = {
350
+ children: null,
351
+ };
352
+
353
+ export default Tooltip;
@@ -0,0 +1,36 @@
1
+ @keyframes appearance {
2
+ from { opacity: 0; }
3
+ to { opacity: 1; }
4
+ }
5
+
6
+ *,
7
+ .ad.hoc,
8
+ .context {
9
+ &.arrow {
10
+ border: 0.6em solid grey;
11
+ pointer-events: none;
12
+ position: absolute;
13
+ width: 0;
14
+ height: 0;
15
+ }
16
+
17
+ /*
18
+ &.content { }
19
+ */
20
+
21
+ &.container {
22
+ background: grey;
23
+ border-radius: 0.3em;
24
+ color: white;
25
+ display: inline-block;
26
+ padding: 0 0.3em;
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ animation: appearance 0.6s;
31
+ }
32
+
33
+ &.wrapper {
34
+ display: inline-block;
35
+ }
36
+ }
@@ -0,0 +1,204 @@
1
+ /* global window */
2
+
3
+ import PT from 'prop-types';
4
+ import {
5
+ type ReactNode,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+
11
+ import themed, { type Theme } from '@dr.pogodin/react-themes';
12
+
13
+ import Tooltip, { PLACEMENTS, type TooltipThemeT } from './Tooltip';
14
+
15
+ import defaultTheme from './default-theme.scss';
16
+
17
+ type PropsT = {
18
+ children?: ReactNode;
19
+ placement?: PLACEMENTS;
20
+ tip?: ReactNode;
21
+ theme: Theme & TooltipThemeT & {
22
+ wrapper?: string;
23
+ };
24
+ };
25
+
26
+ type TooltipRefT = {
27
+ pointTo: (
28
+ x: number,
29
+ y: number,
30
+ placement: PLACEMENTS,
31
+ wrapperRef: HTMLDivElement,
32
+ ) => void;
33
+ };
34
+
35
+ type HeapT = {
36
+ lastCursorX: number;
37
+ lastCursorY: number;
38
+ triggeredByTouch: boolean;
39
+ timerId?: NodeJS.Timeout;
40
+ };
41
+
42
+ /**
43
+ * Implements a simple to use and themeable tooltip component, _e.g._
44
+ * ```js
45
+ * <WithTooltip tip="This is example tooltip.">
46
+ * <p>Hover to see the tooltip.</p>
47
+ * </WithTooltip>
48
+ * ```
49
+ * **Children:** Children are rendered in the place of `<WithTooltip>`,
50
+ * and when hovered the tooltip is shown. By default the wrapper itself is
51
+ * `<div>` block with `display: inline-block`.
52
+ * @param {object} props Component properties.
53
+ * @param {React.node} props.tip &ndash; Anything React is able to render,
54
+ * _e.g._ a tooltip text. This will be the tooltip content.
55
+ * @param {WithTooltipTheme} props.theme _Ad hoc_ theme.
56
+ */
57
+ const Wrapper: React.FunctionComponent<PropsT> = ({
58
+ children,
59
+ placement,
60
+ tip,
61
+ theme,
62
+ }) => {
63
+ const { current: heap } = useRef<HeapT>({
64
+ lastCursorX: 0,
65
+ lastCursorY: 0,
66
+ triggeredByTouch: false,
67
+ timerId: undefined,
68
+ });
69
+ const tooltipRef = useRef<TooltipRefT>();
70
+ const wrapperRef = useRef<HTMLDivElement>(null);
71
+ const [showTooltip, setShowTooltip] = useState(false);
72
+
73
+ const updatePortalPosition = (cursorX: number, cursorY: number) => {
74
+ if (!showTooltip) {
75
+ heap.lastCursorX = cursorX;
76
+ heap.lastCursorY = cursorY;
77
+
78
+ // If tooltip was triggered by a touch, we delay its opening by a bit,
79
+ // to ensure it was not a touch-click - in the case of touch click we
80
+ // want to do the click, rather than show the tooltip, and the delay
81
+ // gives click handler a chance to abort the tooltip openning.
82
+ if (heap.triggeredByTouch) {
83
+ if (!heap.timerId) {
84
+ heap.timerId = setTimeout(() => {
85
+ heap.triggeredByTouch = false;
86
+ heap.timerId = undefined;
87
+ setShowTooltip(true);
88
+ }, 300);
89
+ }
90
+
91
+ // Otherwise we can just open the tooltip right away.
92
+ } else setShowTooltip(true);
93
+ } else {
94
+ const wrapperRect = wrapperRef.current!.getBoundingClientRect();
95
+ if (
96
+ cursorX < wrapperRect.left
97
+ || cursorX > wrapperRect.right
98
+ || cursorY < wrapperRect.top
99
+ || cursorY > wrapperRect.bottom
100
+ ) {
101
+ setShowTooltip(false);
102
+ } else if (tooltipRef.current) {
103
+ tooltipRef.current.pointTo(
104
+ cursorX + window.scrollX,
105
+ cursorY + window.scrollY,
106
+ placement!,
107
+ wrapperRef.current!,
108
+ );
109
+ }
110
+ }
111
+ };
112
+
113
+ useEffect(() => {
114
+ if (showTooltip && tip !== null) {
115
+ // This is necessary to ensure that even when a single mouse event
116
+ // arrives to a tool-tipped component, the tooltip is correctly positioned
117
+ // once opened (because similar call above does not have effect until
118
+ // the tooltip is fully mounted, and that is delayed to future rendering
119
+ // cycle due to the implementation).
120
+ if (tooltipRef.current) {
121
+ tooltipRef.current.pointTo(
122
+ heap.lastCursorX + window.scrollX,
123
+ heap.lastCursorY + window.scrollY,
124
+ placement!,
125
+ wrapperRef.current!,
126
+ );
127
+ }
128
+
129
+ const listener = () => setShowTooltip(false);
130
+ window.addEventListener('scroll', listener);
131
+ return () => window.removeEventListener('scroll', listener);
132
+ }
133
+ return undefined;
134
+ }, [
135
+ heap.lastCursorX,
136
+ heap.lastCursorY,
137
+ placement,
138
+ showTooltip,
139
+ tip,
140
+ ]);
141
+
142
+ return (
143
+ <div
144
+ className={theme.wrapper}
145
+ onMouseLeave={() => setShowTooltip(false)}
146
+ onMouseMove={(e) => updatePortalPosition(e.clientX, e.clientY)}
147
+ onClick={() => {
148
+ if (heap.timerId) {
149
+ clearTimeout(heap.timerId);
150
+ heap.timerId = undefined;
151
+ heap.triggeredByTouch = false;
152
+ }
153
+ }}
154
+ onTouchStart={() => {
155
+ heap.triggeredByTouch = true;
156
+ }}
157
+ ref={wrapperRef}
158
+ role="presentation"
159
+ >
160
+ {
161
+ showTooltip && tip !== null ? (
162
+ <Tooltip ref={tooltipRef} theme={theme}>{tip}</Tooltip>
163
+ ) : null
164
+ }
165
+ {children}
166
+ </div>
167
+ );
168
+ };
169
+
170
+ const ThemedWrapper = themed(
171
+ Wrapper,
172
+ 'WithTooltip',
173
+ [
174
+ 'appearance',
175
+ 'arrow',
176
+ 'container',
177
+ 'content',
178
+ 'wrapper',
179
+ ],
180
+ defaultTheme,
181
+ );
182
+
183
+ type ExportT = typeof ThemedWrapper & {
184
+ PLACEMENTS: typeof PLACEMENTS;
185
+ };
186
+
187
+ const e: ExportT = ThemedWrapper as ExportT;
188
+
189
+ e.PLACEMENTS = PLACEMENTS;
190
+
191
+ Wrapper.propTypes = {
192
+ children: PT.node,
193
+ placement: PT.oneOf(Object.values(PLACEMENTS)),
194
+ theme: ThemedWrapper.themeType.isRequired,
195
+ tip: PT.node,
196
+ };
197
+
198
+ Wrapper.defaultProps = {
199
+ children: null,
200
+ placement: PLACEMENTS.ABOVE_CURSOR,
201
+ tip: null,
202
+ };
203
+
204
+ export default e;
@@ -0,0 +1,13 @@
1
+ *,
2
+ .context,
3
+ .ad.hoc {
4
+ .container {
5
+ background: whitesmoke;
6
+ }
7
+
8
+ .video {
9
+ height: 100%;
10
+ position: relative;
11
+ width: 100%;
12
+ }
13
+ }
@@ -0,0 +1,96 @@
1
+ import PT from 'prop-types';
2
+ import qs from 'qs';
3
+
4
+ import themed, { type Theme } from '@dr.pogodin/react-themes';
5
+
6
+ import ScalableRect from 'components/ScalableRect';
7
+ import Throbber from 'components/Throbber';
8
+
9
+ import baseTheme from './base.scss';
10
+ import throbberTheme from './throbber.scss';
11
+
12
+ type ComponentThemeT = Theme & {
13
+ container?: string;
14
+ video?: string;
15
+ };
16
+
17
+ type PropsT = {
18
+ autoplay?: boolean;
19
+ src: string;
20
+ theme: ComponentThemeT,
21
+ title?: string;
22
+ };
23
+
24
+ /**
25
+ * A component for embeding a YouTube video.
26
+ * @param [props] Component properties.
27
+ * @param [props.autoplay] If `true` the video will start to play
28
+ * automatically once loaded.
29
+ * @param [props.src] URL of the video to play. Can be in any of
30
+ * the following formats, and keeps any additional query parameters understood
31
+ * by the YouTube IFrame player:
32
+ * - `https://www.youtube.com/watch?v=NdF6Rmt6Ado`
33
+ * - `https://youtu.be/NdF6Rmt6Ado`
34
+ * - `https://www.youtube.com/embed/NdF6Rmt6Ado`
35
+ * @param [props.theme] _Ad hoc_ theme.
36
+ * @param [props.title] The `title` attribute to add to the player
37
+ * IFrame.
38
+ */
39
+ const YouTubeVideo: React.FunctionComponent<PropsT> = ({
40
+ autoplay,
41
+ src,
42
+ theme,
43
+ title,
44
+ }) => {
45
+ const srcParts = src.split('?');
46
+ let url = srcParts[0];
47
+ const queryString = srcParts[1];
48
+ const query = queryString ? qs.parse(queryString) : {};
49
+
50
+ const videoId = query.v || url.match(/\/([a-zA-Z0-9-_]*)$/)?.[1];
51
+ url = `https://www.youtube.com/embed/${videoId}`;
52
+
53
+ delete query.v;
54
+ query.autoplay = autoplay ? '1' : '0';
55
+ url += `?${qs.stringify(query)}`;
56
+
57
+ // TODO: https://developers.google.com/youtube/player_parameters
58
+ // More query parameters can be exposed via the component props.
59
+
60
+ return (
61
+ <ScalableRect className={theme.container} ratio="16:9">
62
+ <Throbber theme={throbberTheme} />
63
+ <iframe
64
+ allow="autoplay"
65
+ allowFullScreen
66
+ className={theme.video}
67
+ src={url}
68
+ title={title}
69
+ />
70
+ </ScalableRect>
71
+ );
72
+ };
73
+
74
+ const ThemedYouTubeVideo = themed(
75
+ YouTubeVideo,
76
+ 'YouTubeVideo',
77
+ [
78
+ 'container',
79
+ 'video',
80
+ ],
81
+ baseTheme,
82
+ );
83
+
84
+ YouTubeVideo.propTypes = {
85
+ autoplay: PT.bool,
86
+ src: PT.string.isRequired,
87
+ theme: ThemedYouTubeVideo.themeType.isRequired,
88
+ title: PT.string,
89
+ };
90
+
91
+ YouTubeVideo.defaultProps = {
92
+ autoplay: false,
93
+ title: '',
94
+ };
95
+
96
+ export default ThemedYouTubeVideo;
@@ -0,0 +1,11 @@
1
+ *,
2
+ .context,
3
+ .ad.hoc {
4
+ .container {
5
+ position: absolute;
6
+ text-align: center;
7
+ top: 40%;
8
+ transform: translateY(50%);
9
+ width: 100%;
10
+ }
11
+ }