@stats-forge/github-stats-forge-core 0.0.2 → 0.1.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 (208) hide show
  1. package/README.md +0 -10
  2. package/build/api/api-result.d.ts +6 -7
  3. package/build/api/api-result.d.ts.map +1 -1
  4. package/build/api/api-result.js +4 -5
  5. package/build/api/gist.d.ts +3 -10
  6. package/build/api/gist.d.ts.map +1 -1
  7. package/build/api/gist.js +4 -11
  8. package/build/api/index.d.ts +12 -12
  9. package/build/api/params.d.ts +11 -8
  10. package/build/api/params.d.ts.map +1 -1
  11. package/build/api/params.js +16 -14
  12. package/build/api/pin.d.ts +3 -19
  13. package/build/api/pin.d.ts.map +1 -1
  14. package/build/api/pin.js +6 -22
  15. package/build/api/stats.d.ts +10 -4
  16. package/build/api/stats.d.ts.map +1 -1
  17. package/build/api/stats.js +6 -32
  18. package/build/api/top-langs.d.ts +12 -6
  19. package/build/api/top-langs.d.ts.map +1 -1
  20. package/build/api/top-langs.js +6 -26
  21. package/build/api/wakatime.d.ts +13 -5
  22. package/build/api/wakatime.d.ts.map +1 -1
  23. package/build/api/wakatime.js +4 -21
  24. package/build/calculateRank.d.ts +0 -9
  25. package/build/calculateRank.d.ts.map +1 -1
  26. package/build/calculateRank.js +12 -17
  27. package/build/cards/gist.d.ts +2 -4
  28. package/build/cards/gist.d.ts.map +1 -1
  29. package/build/cards/gist.js +27 -36
  30. package/build/cards/options.d.ts +1 -1
  31. package/build/cards/repo.d.ts +2 -4
  32. package/build/cards/repo.d.ts.map +1 -1
  33. package/build/cards/repo.js +61 -93
  34. package/build/cards/stats.d.ts +3 -8
  35. package/build/cards/stats.d.ts.map +1 -1
  36. package/build/cards/stats.js +119 -165
  37. package/build/cards/top-languages.d.ts +5 -31
  38. package/build/cards/top-languages.d.ts.map +1 -1
  39. package/build/cards/top-languages.js +176 -384
  40. package/build/cards/wakatime.d.ts +4 -6
  41. package/build/cards/wakatime.d.ts.map +1 -1
  42. package/build/cards/wakatime.js +118 -185
  43. package/build/common/Card.d.ts +14 -62
  44. package/build/common/Card.d.ts.map +1 -1
  45. package/build/common/Card.js +97 -199
  46. package/build/common/I18n.d.ts +0 -6
  47. package/build/common/I18n.d.ts.map +1 -1
  48. package/build/common/I18n.js +3 -6
  49. package/build/common/color.d.ts +4 -17
  50. package/build/common/color.d.ts.map +1 -1
  51. package/build/common/color.js +67 -49
  52. package/build/common/config.d.ts +4 -14
  53. package/build/common/config.d.ts.map +1 -1
  54. package/build/common/config.js +3 -16
  55. package/build/common/constants.d.ts +4 -2
  56. package/build/common/constants.d.ts.map +1 -1
  57. package/build/common/constants.js +3 -1
  58. package/build/common/date.d.ts +0 -2
  59. package/build/common/date.d.ts.map +1 -1
  60. package/build/common/date.js +0 -2
  61. package/build/common/emojiMap.d.ts +8 -0
  62. package/build/common/emojiMap.d.ts.map +1 -0
  63. package/build/common/emojiMap.js +10 -0
  64. package/build/common/emojiMap.json +1915 -0
  65. package/build/common/error.d.ts +1 -10
  66. package/build/common/error.d.ts.map +1 -1
  67. package/build/common/error.js +1 -10
  68. package/build/common/fmt.d.ts +3 -15
  69. package/build/common/fmt.d.ts.map +1 -1
  70. package/build/common/fmt.js +7 -35
  71. package/build/common/http.d.ts +2 -7
  72. package/build/common/http.d.ts.map +1 -1
  73. package/build/common/http.js +8 -17
  74. package/build/common/icons.d.ts +18 -20
  75. package/build/common/icons.d.ts.map +1 -1
  76. package/build/common/icons.js +103 -67
  77. package/build/common/languageColors.d.ts +0 -1
  78. package/build/common/languageColors.d.ts.map +1 -1
  79. package/build/common/languageColors.js +1 -4
  80. package/build/common/languageColors.json +18 -2
  81. package/build/common/ops.d.ts +3 -14
  82. package/build/common/ops.d.ts.map +1 -1
  83. package/build/common/ops.js +19 -30
  84. package/build/common/render.d.ts +28 -82
  85. package/build/common/render.d.ts.map +1 -1
  86. package/build/common/render.js +175 -211
  87. package/build/common/retryer.d.ts +2 -7
  88. package/build/common/retryer.d.ts.map +1 -1
  89. package/build/common/retryer.js +4 -11
  90. package/build/fetchers/gist.d.ts +2 -5
  91. package/build/fetchers/gist.d.ts.map +1 -1
  92. package/build/fetchers/gist.js +1 -5
  93. package/build/fetchers/index.d.ts +12 -12
  94. package/build/fetchers/repo.d.ts +2 -11
  95. package/build/fetchers/repo.d.ts.map +1 -1
  96. package/build/fetchers/repo.js +20 -33
  97. package/build/fetchers/stats.d.ts +2 -32
  98. package/build/fetchers/stats.d.ts.map +1 -1
  99. package/build/fetchers/stats.js +27 -86
  100. package/build/fetchers/top-languages.d.ts +2 -9
  101. package/build/fetchers/top-languages.d.ts.map +1 -1
  102. package/build/fetchers/top-languages.js +15 -31
  103. package/build/fetchers/types.d.ts +1 -1
  104. package/build/fetchers/wakatime.d.ts +2 -6
  105. package/build/fetchers/wakatime.d.ts.map +1 -1
  106. package/build/fetchers/wakatime.js +1 -5
  107. package/build/graphql/contributionsDocument.d.ts +3 -3
  108. package/build/graphql/contributionsDocument.d.ts.map +1 -1
  109. package/build/graphql/contributionsDocument.js +0 -1
  110. package/build/graphql/generated/gist.d.ts +1 -1
  111. package/build/graphql/generated/repo.d.ts +1 -1
  112. package/build/graphql/generated/stats.d.ts +3 -3
  113. package/build/graphql/generated/top-languages.d.ts +2 -2
  114. package/build/graphql/graphqlDocument.d.ts +0 -1
  115. package/build/graphql/graphqlDocument.d.ts.map +1 -1
  116. package/build/graphql/graphqlDocument.js +0 -1
  117. package/build/graphql/reposContributedToDocument.d.ts +4 -5
  118. package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
  119. package/build/graphql/reposContributedToDocument.js +0 -2
  120. package/build/markup/css.d.ts +35 -0
  121. package/build/markup/css.d.ts.map +1 -0
  122. package/build/markup/css.js +64 -0
  123. package/build/markup/element.d.ts +41 -0
  124. package/build/markup/element.d.ts.map +1 -0
  125. package/build/markup/element.js +127 -0
  126. package/build/markup/html.d.ts +13 -0
  127. package/build/markup/html.d.ts.map +1 -0
  128. package/build/markup/html.js +27 -0
  129. package/build/markup/index.d.ts +9 -0
  130. package/build/markup/index.d.ts.map +1 -0
  131. package/build/markup/index.js +6 -0
  132. package/build/markup/serialize.d.ts +16 -0
  133. package/build/markup/serialize.d.ts.map +1 -0
  134. package/build/markup/serialize.js +17 -0
  135. package/build/themes/index.d.ts +332 -333
  136. package/build/themes/index.d.ts.map +1 -1
  137. package/build/themes/index.js +1 -1
  138. package/build/translations.d.ts +2 -6
  139. package/build/translations.d.ts.map +1 -1
  140. package/build/translations.js +621 -628
  141. package/package.json +17 -26
  142. package/src/api/api-result.ts +10 -9
  143. package/src/api/gist.ts +11 -18
  144. package/src/api/index.ts +12 -12
  145. package/src/api/params.ts +27 -21
  146. package/src/api/pin.ts +13 -28
  147. package/src/api/stats.ts +13 -39
  148. package/src/api/top-langs.ts +13 -33
  149. package/src/api/wakatime.ts +11 -28
  150. package/src/calculateRank.ts +12 -23
  151. package/src/cards/gist.ts +40 -43
  152. package/src/cards/options.ts +1 -1
  153. package/src/cards/repo.ts +97 -107
  154. package/src/cards/stats.ts +165 -184
  155. package/src/cards/top-languages.ts +297 -413
  156. package/src/cards/wakatime.ts +193 -221
  157. package/src/common/Card.ts +165 -217
  158. package/src/common/I18n.ts +3 -6
  159. package/src/common/color.ts +80 -57
  160. package/src/common/config.ts +5 -18
  161. package/src/common/constants.ts +5 -2
  162. package/src/common/date.ts +0 -2
  163. package/src/common/emojiMap.json +1915 -0
  164. package/src/common/emojiMap.ts +13 -0
  165. package/src/common/error.ts +2 -11
  166. package/src/common/fmt.ts +7 -44
  167. package/src/common/http.ts +9 -15
  168. package/src/common/icons.ts +117 -42
  169. package/src/common/languageColors.json +18 -2
  170. package/src/common/languageColors.ts +1 -4
  171. package/src/common/ops.ts +26 -34
  172. package/src/common/render.ts +279 -232
  173. package/src/common/retryer.ts +8 -15
  174. package/src/fetchers/gist.ts +8 -12
  175. package/src/fetchers/index.ts +12 -12
  176. package/src/fetchers/repo.ts +27 -40
  177. package/src/fetchers/stats.ts +44 -102
  178. package/src/fetchers/top-languages.ts +26 -40
  179. package/src/fetchers/types.ts +1 -1
  180. package/src/fetchers/wakatime.ts +5 -9
  181. package/src/graphql/contributionsDocument.ts +7 -5
  182. package/src/graphql/generated/gist.ts +1 -1
  183. package/src/graphql/generated/repo.ts +1 -1
  184. package/src/graphql/generated/stats.ts +2 -2
  185. package/src/graphql/generated/top-languages.ts +2 -2
  186. package/src/graphql/graphqlDocument.ts +0 -1
  187. package/src/graphql/reposContributedToDocument.ts +6 -7
  188. package/src/markup/css.ts +118 -0
  189. package/src/markup/element.ts +246 -0
  190. package/src/markup/html.ts +34 -0
  191. package/src/markup/index.ts +9 -0
  192. package/src/markup/serialize.ts +27 -0
  193. package/src/themes/index.ts +1 -1
  194. package/src/translations.ts +622 -629
  195. package/build/cards/index.d.ts +0 -11
  196. package/build/cards/index.d.ts.map +0 -1
  197. package/build/cards/index.js +0 -7
  198. package/build/common/html.d.ts +0 -8
  199. package/build/common/html.d.ts.map +0 -1
  200. package/build/common/html.js +0 -14
  201. package/build/index.d.ts +0 -15
  202. package/build/index.d.ts.map +0 -1
  203. package/build/index.js +0 -10
  204. package/src/_emoji-name-map.d.ts +0 -10
  205. package/src/_github-username-regex.d.ts +0 -4
  206. package/src/cards/index.ts +0 -21
  207. package/src/common/html.ts +0 -17
  208. package/src/index.ts +0 -18
@@ -1,15 +1,16 @@
1
- import { Card } from '../common/Card.js';
2
- import { getLightDarkColors, isPrefixedHexColor } from '../common/color.js';
3
- import { formatBytes } from '../common/fmt.js';
4
- import { encodeHTML } from '../common/html.js';
5
- import { I18n } from '../common/I18n.js';
6
- import { DEFAULT_LANG_COLOR } from '../common/languageColors.js';
7
- import { chunkArray, clampValue, lowercaseTrim } from '../common/ops.js';
8
- import { createProgressNode, flexLayout, measureText } from '../common/render.js';
9
- import type { Lang, TopLangData } from '../fetchers/types.js';
10
- import { langCardLocales } from '../translations.js';
11
-
12
- import type { CardOptions, CommonCardOptions } from './options.js';
1
+ import { Card } from '../common/Card.ts';
2
+ import { getLightDarkColors, isPrefixedHexColor } from '../common/color.ts';
3
+ import { formatBytes } from '../common/fmt.ts';
4
+ import { I18n } from '../common/I18n.ts';
5
+ import { DEFAULT_LANG_COLOR } from '../common/languageColors.ts';
6
+ import { chunkArray, clampValue, lowercaseTrim } from '../common/ops.ts';
7
+ import { createProgressNode, flexLayout, measureText } from '../common/render.ts';
8
+ import type { Lang, TopLangData } from '../fetchers/types.ts';
9
+ import type { Child, MarkupElement } from '../markup/index.ts';
10
+ import { atRule, cssComment, el, rule } from '../markup/index.ts';
11
+ import { langCardLocales } from '../translations.ts';
12
+
13
+ import type { CardOptions, CommonCardOptions } from './options.ts';
13
14
 
14
15
  const DEFAULT_CARD_WIDTH = 300;
15
16
  const MIN_CARD_WIDTH = 280;
@@ -49,38 +50,35 @@ interface TopLangOptions extends CommonCardOptions {
49
50
  /**
50
51
  * Retrieves the programming language whose name is the longest.
51
52
  *
52
- * @param arr Array of programming languages.
53
53
  * @returns Longest programming language object.
54
54
  */
55
- const getLongestLang = (arr: Array<Lang>): Pick<Lang, 'name' | 'size' | 'color'> =>
56
- arr.reduce<Pick<Lang, 'name' | 'size' | 'color'>>(
57
- (savedLang, lang) => (lang.name.length > savedLang.name.length ? lang : savedLang),
58
- { name: '', size: 0, color: '' },
59
- );
55
+ const getLongestLang = (arr: Array<Lang>): Pick<Lang, 'name' | 'size' | 'color'> => {
56
+ let longest: Pick<Lang, 'name' | 'size' | 'color'> = { name: '', size: 0, color: '' };
57
+ for (const lang of arr) {
58
+ if (lang.name.length > longest.name.length) {
59
+ longest = lang;
60
+ }
61
+ }
62
+ return longest;
63
+ };
60
64
 
61
65
  /**
62
66
  * Convert degrees to radians.
63
67
  *
64
- * @param angleInDegrees Angle in degrees.
65
68
  * @returns Angle in radians.
66
69
  */
67
- const degreesToRadians = (angleInDegrees: number): number => angleInDegrees * (Math.PI / 180.0);
70
+ const degreesToRadians = (angleInDegrees: number): number => angleInDegrees * (Math.PI / 180);
68
71
 
69
72
  /**
70
73
  * Convert radians to degrees.
71
74
  *
72
- * @param angleInRadians Angle in radians.
73
75
  * @returns Angle in degrees.
74
76
  */
75
- const radiansToDegrees = (angleInRadians: number): number => angleInRadians / (Math.PI / 180.0);
77
+ const radiansToDegrees = (angleInRadians: number): number => angleInRadians / (Math.PI / 180);
76
78
 
77
79
  /**
78
80
  * Convert polar coordinates to cartesian coordinates.
79
81
  *
80
- * @param centerX Center x coordinate.
81
- * @param centerY Center y coordinate.
82
- * @param radius Radius of the circle.
83
- * @param angleInDegrees Angle in degrees.
84
82
  * @returns Cartesian coordinates.
85
83
  */
86
84
  const polarToCartesian = (
@@ -99,10 +97,6 @@ const polarToCartesian = (
99
97
  /**
100
98
  * Convert cartesian coordinates to polar coordinates.
101
99
  *
102
- * @param centerX Center x coordinate.
103
- * @param centerY Center y coordinate.
104
- * @param x Point x coordinate.
105
- * @param y Point y coordinate.
106
100
  * @returns Polar coordinates.
107
101
  */
108
102
  const cartesianToPolar = (
@@ -111,7 +105,7 @@ const cartesianToPolar = (
111
105
  x: number,
112
106
  y: number,
113
107
  ): { radius: number; angleInDegrees: number } => {
114
- const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
108
+ const radius = Math.hypot(x - centerX, y - centerY);
115
109
  let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX));
116
110
  if (angleInDegrees < 0) {
117
111
  angleInDegrees += 360;
@@ -122,79 +116,60 @@ const cartesianToPolar = (
122
116
  /**
123
117
  * Calculates length of circle.
124
118
  *
125
- * @param radius Radius of the circle.
126
119
  * @returns The length of the circle.
127
120
  */
128
- const getCircleLength = (radius: number): number => {
129
- return 2 * Math.PI * radius;
130
- };
121
+ const getCircleLength = (radius: number): number => 2 * Math.PI * radius;
131
122
 
132
123
  /**
133
124
  * Calculates height for the compact layout.
134
125
  *
135
- * @param totalLangs Total number of languages.
136
126
  * @returns Card height.
137
127
  */
138
- const calculateCompactLayoutHeight = (totalLangs: number): number => {
139
- return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
140
- };
128
+ const calculateCompactLayoutHeight = (totalLangs: number): number =>
129
+ COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
141
130
 
142
131
  /**
143
132
  * Calculates height for the normal layout.
144
133
  *
145
- * @param totalLangs Total number of languages.
146
134
  * @returns Card height.
147
135
  */
148
- const calculateNormalLayoutHeight = (totalLangs: number): number => {
149
- return 45 + (totalLangs + 1) * 40;
150
- };
136
+ const calculateNormalLayoutHeight = (totalLangs: number): number => 45 + (totalLangs + 1) * 40;
151
137
 
152
138
  /**
153
139
  * Calculates height for the donut layout.
154
140
  *
155
- * @param totalLangs Total number of languages.
156
141
  * @returns Card height.
157
142
  */
158
- const calculateDonutLayoutHeight = (totalLangs: number): number => {
159
- return 215 + Math.max(totalLangs - 5, 0) * 32;
160
- };
143
+ const calculateDonutLayoutHeight = (totalLangs: number): number =>
144
+ 215 + Math.max(totalLangs - 5, 0) * 32;
161
145
 
162
146
  /**
163
147
  * Calculates height for the donut vertical layout.
164
148
  *
165
- * @param totalLangs Total number of languages.
166
149
  * @returns Card height.
167
150
  */
168
- const calculateDonutVerticalLayoutHeight = (totalLangs: number): number => {
169
- return 300 + Math.round(totalLangs / 2) * 25;
170
- };
151
+ const calculateDonutVerticalLayoutHeight = (totalLangs: number): number =>
152
+ 300 + Math.round(totalLangs / 2) * 25;
171
153
 
172
154
  /**
173
155
  * Calculates height for the pie layout.
174
156
  *
175
- * @param totalLangs Total number of languages.
176
157
  * @returns Card height.
177
158
  */
178
- const calculatePieLayoutHeight = (totalLangs: number): number => {
179
- return 300 + Math.round(totalLangs / 2) * 25;
180
- };
159
+ const calculatePieLayoutHeight = (totalLangs: number): number =>
160
+ 300 + Math.round(totalLangs / 2) * 25;
181
161
 
182
162
  /**
183
163
  * Calculates the center translation needed to keep the donut chart centred.
184
164
  *
185
- * @param totalLangs Total number of languages.
186
165
  * @returns Donut center translation.
187
166
  */
188
- const donutCenterTranslation = (totalLangs: number): number => {
189
- return -45 + Math.max(totalLangs - 5, 0) * 16;
190
- };
167
+ const donutCenterTranslation = (totalLangs: number): number =>
168
+ -45 + Math.max(totalLangs - 5, 0) * 16;
191
169
 
192
170
  /**
193
171
  * Trim top languages to lang_count while also hiding certain languages.
194
172
  *
195
- * @param topLangs Top languages.
196
- * @param langs_count Number of languages to show.
197
- * @param hide Languages to hide.
198
173
  * @returns Trimmed top languages and total size.
199
174
  */
200
175
  const trimTopLanguages = (
@@ -208,14 +183,14 @@ const trimTopLanguages = (
208
183
 
209
184
  // populate langsToHide map for quick lookup while filtering out
210
185
  if (hide) {
211
- hide.forEach((langName) => {
186
+ for (const langName of hide) {
212
187
  langsToHide[lowercaseTrim(langName)] = true;
213
- });
188
+ }
214
189
  }
215
190
 
216
191
  // filter out languages to be hidden
217
192
  langs = langs
218
- .sort((a, b) => b.size - a.size)
193
+ .toSorted((a, b) => b.size - a.size)
219
194
  .filter((lang) => !langsToHide[lowercaseTrim(lang.name)])
220
195
  .slice(0, langsCount);
221
196
 
@@ -227,20 +202,15 @@ const trimTopLanguages = (
227
202
  /**
228
203
  * Get display value corresponding to the format.
229
204
  *
230
- * @param size Bytes size.
231
- * @param percentages Percentage value.
232
- * @param format Format of the stats.
233
205
  * @returns Display value.
234
206
  */
235
- const getDisplayValue = (size: number, percentages: number, format: string): string => {
236
- return format === 'bytes' ? formatBytes(size) : `${percentages.toFixed(2)}%`;
237
- };
207
+ const getDisplayValue = (size: number, percentages: number, format: string): string =>
208
+ format === 'bytes' ? formatBytes(size) : `${percentages.toFixed(2)}%`;
238
209
 
239
210
  /**
240
211
  * Resolves a language's display color, falling back to the default, and
241
212
  * validates it is a prefixed hex color.
242
213
  *
243
- * @param lang Programming language object.
244
214
  * @returns Validated language color.
245
215
  */
246
216
  const resolveLangColor = (lang: Lang): string => {
@@ -254,15 +224,6 @@ const resolveLangColor = (lang: Lang): string => {
254
224
  /**
255
225
  * Create progress bar text item for a programming language.
256
226
  *
257
- * @param props Function properties.
258
- * @param props.width The card width
259
- * @param props.color Color of the programming language.
260
- * @param props.name Name of the programming language.
261
- * @param props.size Size of the programming language.
262
- * @param props.totalSize Total size of all languages.
263
- * @param props.statsFormat Stats format.
264
- * @param props.hideValues Whether to hide stats values.
265
- * @param props.index Index of the programming language.
266
227
  * @returns Programming language SVG node.
267
228
  */
268
229
  const createProgressTextNode = ({
@@ -283,7 +244,7 @@ const createProgressTextNode = ({
283
244
  statsFormat: string;
284
245
  hideValues?: boolean | undefined;
285
246
  index: number;
286
- }): string => {
247
+ }): MarkupElement => {
287
248
  const staggerDelay = (index + 3) * 150;
288
249
  const paddingRight = hideValues ? CARD_PADDING * 2 : 95;
289
250
  const progressTextX = width - paddingRight + 10;
@@ -292,32 +253,25 @@ const createProgressTextNode = ({
292
253
  const progress = (size / totalSize) * 100;
293
254
  const displayValue = getDisplayValue(size, progress, statsFormat);
294
255
 
295
- return `
296
- <g class="stagger" style="animation-delay: ${staggerDelay}ms">
297
- <text data-testid="lang-name" x="2" y="15" class="lang-name">${encodeHTML(name)}</text>
298
- ${hideValues ? '' : `<text x="${progressTextX}" y="34" class="lang-name">${encodeHTML(displayValue)}</text>`}
299
- ${createProgressNode({
300
- x: 0,
301
- y: 25,
302
- color,
303
- width: progressWidth,
304
- progress,
305
- delay: staggerDelay + 300,
306
- })}
307
- </g>
308
- `;
256
+ return el(
257
+ 'g',
258
+ { class: 'stagger', style: `animation-delay: ${staggerDelay}ms` },
259
+ el('text', { 'data-testid': 'lang-name', x: 2, y: 15, class: 'lang-name' }, name),
260
+ !hideValues && el('text', { x: progressTextX, y: 34, class: 'lang-name' }, displayValue),
261
+ createProgressNode({
262
+ x: 0,
263
+ y: 25,
264
+ color,
265
+ width: progressWidth,
266
+ progress,
267
+ delay: staggerDelay + 300,
268
+ }),
269
+ );
309
270
  };
310
271
 
311
272
  /**
312
273
  * Creates compact text item for a programming language.
313
274
  *
314
- * @param props Function properties.
315
- * @param props.lang Programming language object.
316
- * @param props.totalSize Total size of all languages.
317
- * @param props.hideProgress Whether to hide percentage.
318
- * @param props.hideValues Whether to hide stats values (percentages/bytes).
319
- * @param props.statsFormat Stats format
320
- * @param props.index Index of the programming language.
321
275
  * @returns Compact layout programming language SVG node.
322
276
  */
323
277
  const createCompactLangNode = ({
@@ -334,32 +288,26 @@ const createCompactLangNode = ({
334
288
  hideValues?: boolean | undefined;
335
289
  statsFormat?: string | undefined;
336
290
  index: number;
337
- }): string => {
291
+ }): MarkupElement => {
338
292
  const percentages = (lang.size / totalSize) * 100;
339
293
  const displayValue = getDisplayValue(lang.size, percentages, statsFormat);
340
294
 
341
295
  const staggerDelay = (index + 3) * 150;
342
296
  const color = resolveLangColor(lang);
343
297
 
344
- return `
345
- <g class="stagger" style="animation-delay: ${staggerDelay}ms">
346
- <circle cx="5" cy="6" r="5" fill="${color}" />
347
- <text data-testid="lang-name" x="15" y="10" class='lang-name'>
348
- ${encodeHTML(lang.name)} ${hideProgress || hideValues ? '' : encodeHTML(displayValue)}
349
- </text>
350
- </g>
351
- `;
298
+ const label = hideProgress || hideValues ? lang.name : `${lang.name} ${displayValue}`;
299
+
300
+ return el(
301
+ 'g',
302
+ { class: 'stagger', style: `animation-delay: ${staggerDelay}ms` },
303
+ el('circle', { cx: 5, cy: 6, r: 5, fill: color }),
304
+ el('text', { 'data-testid': 'lang-name', x: 15, y: 10, class: 'lang-name' }, label),
305
+ );
352
306
  };
353
307
 
354
308
  /**
355
309
  * Create compact languages text items for all programming languages.
356
310
  *
357
- * @param props Function properties.
358
- * @param props.langs Array of programming languages.
359
- * @param props.totalSize Total size of all languages.
360
- * @param props.hideProgress Whether to hide percentage.
361
- * @param props.hideValues Whether to hide stats values.
362
- * @param props.statsFormat Stats format
363
311
  * @returns Programming languages SVG node.
364
312
  */
365
313
  const createLanguageTextNode = ({
@@ -374,7 +322,7 @@ const createLanguageTextNode = ({
374
322
  hideProgress?: boolean | undefined;
375
323
  hideValues?: boolean | undefined;
376
324
  statsFormat?: string | undefined;
377
- }): string => {
325
+ }): Array<Child> => {
378
326
  const longestLang = getLongestLang(langs);
379
327
  const chunked = chunkArray(langs, langs.length / 2);
380
328
  const layouts = chunked.map((array) => {
@@ -388,30 +336,18 @@ const createLanguageTextNode = ({
388
336
  index,
389
337
  }),
390
338
  );
391
- return flexLayout({
392
- items,
393
- gap: 25,
394
- direction: 'column',
395
- }).join('');
339
+ return flexLayout({ items, gap: 25, direction: 'column' });
396
340
  });
397
341
 
398
342
  const percent = ((longestLang.size / totalSize) * 100).toFixed(2);
399
343
  const minGap = 150;
400
344
  const maxGap = 20 + measureText(`${longestLang.name} ${percent}%`, 11);
401
- return flexLayout({
402
- items: layouts,
403
- gap: maxGap < minGap ? minGap : maxGap,
404
- }).join('');
345
+ return flexLayout({ items: layouts, gap: maxGap < minGap ? minGap : maxGap });
405
346
  };
406
347
 
407
348
  /**
408
349
  * Create donut languages text items for all programming languages.
409
350
  *
410
- * @param props Function properties.
411
- * @param props.langs Array of programming languages.
412
- * @param props.totalSize Total size of all languages.
413
- * @param props.hideValues Whether to hide stats values.
414
- * @param props.statsFormat Stats format
415
351
  * @returns Donut layout programming language SVG node.
416
352
  */
417
353
  const createDonutLanguagesNode = ({
@@ -424,31 +360,25 @@ const createDonutLanguagesNode = ({
424
360
  totalSize: number;
425
361
  hideValues?: boolean | undefined;
426
362
  statsFormat?: string | undefined;
427
- }): string => {
428
- return flexLayout({
429
- items: langs.map((lang, index) => {
430
- return createCompactLangNode({
363
+ }): Array<Child> =>
364
+ flexLayout({
365
+ items: langs.map((lang, index) =>
366
+ createCompactLangNode({
431
367
  lang,
432
368
  totalSize,
433
369
  hideProgress: false,
434
370
  hideValues,
435
371
  statsFormat,
436
372
  index,
437
- });
438
- }),
373
+ }),
374
+ ),
439
375
  gap: 32,
440
376
  direction: 'column',
441
- }).join('');
442
- };
377
+ });
443
378
 
444
379
  /**
445
380
  * Renders the default language card layout.
446
381
  *
447
- * @param langs Array of programming languages.
448
- * @param width Card width.
449
- * @param totalLanguageSize Total size of all languages.
450
- * @param statsFormat Stats format.
451
- * @param hideValues Whether to hide stats values.
452
382
  * @returns Normal layout card SVG object.
453
383
  */
454
384
  const renderNormalLayout = (
@@ -457,10 +387,10 @@ const renderNormalLayout = (
457
387
  totalLanguageSize: number,
458
388
  statsFormat: string,
459
389
  hideValues?: boolean,
460
- ): string => {
461
- return flexLayout({
462
- items: langs.map((lang, index) => {
463
- return createProgressTextNode({
390
+ ): Array<Child> =>
391
+ flexLayout({
392
+ items: langs.map((lang, index) =>
393
+ createProgressTextNode({
464
394
  width,
465
395
  name: lang.name,
466
396
  color: lang.color || DEFAULT_LANG_COLOR,
@@ -469,22 +399,15 @@ const renderNormalLayout = (
469
399
  statsFormat,
470
400
  hideValues,
471
401
  index,
472
- });
473
- }),
402
+ }),
403
+ ),
474
404
  gap: 40,
475
405
  direction: 'column',
476
- }).join('');
477
- };
406
+ });
478
407
 
479
408
  /**
480
409
  * Renders the compact language card layout.
481
410
  *
482
- * @param langs Array of programming languages.
483
- * @param width Card width.
484
- * @param totalLanguageSize Total size of all languages.
485
- * @param hideProgress Whether to hide progress bar.
486
- * @param statsFormat Stats format.
487
- * @param hideValues Whether to hide stats values.
488
411
  * @returns Compact layout card SVG object.
489
412
  */
490
413
  const renderCompactLayout = (
@@ -494,66 +417,56 @@ const renderCompactLayout = (
494
417
  hideProgress?: boolean,
495
418
  statsFormat = 'percentages',
496
419
  hideValues?: boolean,
497
- ): string => {
420
+ ): Array<Child> => {
498
421
  const paddingRight = 50;
499
422
  const offsetWidth = width - paddingRight;
500
423
  // progressOffset holds the previous language's width and used to offset the next language
501
424
  // so that we can stack them one after another, like this: [--][----][---]
502
425
  let progressOffset = 0;
503
- const compactProgressBar = langs
504
- .map((lang) => {
505
- const langColor = resolveLangColor(lang);
506
-
507
- const percentage = parseFloat(((lang.size / totalLanguageSize) * offsetWidth).toFixed(2));
508
-
509
- const progress = percentage < 10 ? percentage + 10 : percentage;
510
-
511
- const output = `
512
- <rect
513
- mask="url(#rect-mask)"
514
- data-testid="lang-progress"
515
- x="${progressOffset}"
516
- y="0"
517
- width="${progress}"
518
- height="8"
519
- fill="${langColor}"
520
- />
521
- `;
522
- progressOffset += percentage;
523
- return output;
524
- })
525
- .join('');
526
-
527
- return `
528
- ${
529
- hideProgress
530
- ? ''
531
- : `
532
- <mask id="rect-mask">
533
- <rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5"/>
534
- </mask>
535
- ${compactProgressBar}
536
- `
537
- }
538
- <g transform="translate(0, ${hideProgress ? '0' : '25'})">
539
- ${createLanguageTextNode({
426
+ const compactProgressBar = langs.map((lang) => {
427
+ const percentage = Number.parseFloat(
428
+ ((lang.size / totalLanguageSize) * offsetWidth).toFixed(2),
429
+ );
430
+ const x = progressOffset;
431
+ progressOffset += percentage;
432
+
433
+ return el('rect', {
434
+ mask: 'url(#rect-mask)',
435
+ 'data-testid': 'lang-progress',
436
+ x,
437
+ y: 0,
438
+ width: percentage < 10 ? percentage + 10 : percentage,
439
+ height: 8,
440
+ fill: resolveLangColor(lang),
441
+ });
442
+ });
443
+
444
+ return [
445
+ !hideProgress && [
446
+ el(
447
+ 'mask',
448
+ { id: 'rect-mask' },
449
+ el('rect', { x: 0, y: 0, width: offsetWidth, height: 8, fill: 'white', rx: 5 }),
450
+ ),
451
+ compactProgressBar,
452
+ ],
453
+ el(
454
+ 'g',
455
+ { transform: `translate(0, ${hideProgress ? 0 : 25})` },
456
+ createLanguageTextNode({
540
457
  langs,
541
458
  totalSize: totalLanguageSize,
542
459
  hideProgress,
543
460
  statsFormat,
544
461
  hideValues,
545
- })}
546
- </g>
547
- `;
462
+ }),
463
+ ),
464
+ ];
548
465
  };
549
466
 
550
467
  /**
551
468
  * Renders donut vertical layout to display user's most frequently used programming languages.
552
469
  *
553
- * @param langs Array of programming languages.
554
- * @param totalLanguageSize Total size of all languages.
555
- * @param statsFormat Stats format.
556
- * @param hideValues Whether to hide stats values.
557
470
  * @returns Compact layout card SVG object.
558
471
  */
559
472
  const renderDonutVerticalLayout = (
@@ -561,7 +474,7 @@ const renderDonutVerticalLayout = (
561
474
  totalLanguageSize: number,
562
475
  statsFormat: string,
563
476
  hideValues?: boolean,
564
- ): string => {
477
+ ): MarkupElement => {
565
478
  // Donut vertical chart radius and total length
566
479
  const radius = 80;
567
480
  const totalCircleLength = getCircleLength(radius);
@@ -583,22 +496,24 @@ const renderDonutVerticalLayout = (
583
496
  const circleLength = totalCircleLength * (percentage / 100);
584
497
  const delay = startDelayCoefficient * 100;
585
498
 
586
- circles.push(`
587
- <g class="stagger" style="animation-delay: ${delay}ms">
588
- <circle
589
- cx="150"
590
- cy="100"
591
- r="${radius}"
592
- fill="transparent"
593
- stroke="${langColor}"
594
- stroke-width="25"
595
- stroke-dasharray="${totalCircleLength}"
596
- stroke-dashoffset="${indent}"
597
- size="${percentage}"
598
- data-testid="lang-donut"
599
- />
600
- </g>
601
- `);
499
+ circles.push(
500
+ el(
501
+ 'g',
502
+ { class: 'stagger', style: `animation-delay: ${delay}ms` },
503
+ el('circle', {
504
+ cx: 150,
505
+ cy: 100,
506
+ r: radius,
507
+ fill: 'transparent',
508
+ stroke: langColor,
509
+ 'stroke-width': 25,
510
+ 'stroke-dasharray': totalCircleLength,
511
+ 'stroke-dashoffset': indent,
512
+ size: percentage,
513
+ 'data-testid': 'lang-donut',
514
+ }),
515
+ ),
516
+ );
602
517
 
603
518
  // Update the indent for the next part
604
519
  indent += circleLength;
@@ -606,35 +521,31 @@ const renderDonutVerticalLayout = (
606
521
  startDelayCoefficient += 1;
607
522
  }
608
523
 
609
- return `
610
- <svg data-testid="lang-items">
611
- <g transform="translate(0, 0)">
612
- <svg data-testid="donut">
613
- ${circles.join('')}
614
- </svg>
615
- </g>
616
- <g transform="translate(0, 220)">
617
- <svg data-testid="lang-names" x="${CARD_PADDING}">
618
- ${createLanguageTextNode({
619
- langs,
620
- totalSize: totalLanguageSize,
621
- hideProgress: false,
622
- statsFormat,
623
- hideValues,
624
- })}
625
- </svg>
626
- </g>
627
- </svg>
628
- `;
524
+ return el(
525
+ 'svg',
526
+ { 'data-testid': 'lang-items' },
527
+ el('svg', { 'data-testid': 'donut' }, circles),
528
+ el(
529
+ 'g',
530
+ { transform: 'translate(0, 220)' },
531
+ el(
532
+ 'svg',
533
+ { 'data-testid': 'lang-names', x: CARD_PADDING },
534
+ createLanguageTextNode({
535
+ langs,
536
+ totalSize: totalLanguageSize,
537
+ hideProgress: false,
538
+ statsFormat,
539
+ hideValues,
540
+ }),
541
+ ),
542
+ ),
543
+ );
629
544
  };
630
545
 
631
546
  /**
632
547
  * Renders pie layout to display user's most frequently used programming languages.
633
548
  *
634
- * @param langs Array of programming languages.
635
- * @param totalLanguageSize Total size of all languages.
636
- * @param statsFormat Stats format.
637
- * @param hideValues Whether to hide stats values.
638
549
  * @returns Compact layout card SVG object.
639
550
  */
640
551
  const renderPieLayout = (
@@ -642,7 +553,7 @@ const renderPieLayout = (
642
553
  totalLanguageSize: number,
643
554
  statsFormat: string,
644
555
  hideValues?: boolean,
645
- ): string => {
556
+ ): MarkupElement => {
646
557
  // Pie chart radius and center coordinates
647
558
  const radius = 90;
648
559
  const centerX = 150;
@@ -662,17 +573,17 @@ const renderPieLayout = (
662
573
  const langColor = resolveLangColor(lang);
663
574
 
664
575
  if (langs.length === 1) {
665
- paths.push(`
666
- <circle
667
- cx="${centerX}"
668
- cy="${centerY}"
669
- r="${radius}"
670
- stroke="none"
671
- fill="${langColor}"
672
- data-testid="lang-pie"
673
- size="100"
674
- />
675
- `);
576
+ paths.push(
577
+ el('circle', {
578
+ cx: centerX,
579
+ cy: centerY,
580
+ r: radius,
581
+ stroke: 'none',
582
+ fill: langColor,
583
+ 'data-testid': 'lang-pie',
584
+ size: 100,
585
+ }),
586
+ );
676
587
  break;
677
588
  }
678
589
 
@@ -695,16 +606,18 @@ const renderPieLayout = (
695
606
  const delay = startDelayCoefficient * 100;
696
607
 
697
608
  // SVG arc markup
698
- paths.push(`
699
- <g class="stagger" style="animation-delay: ${delay}ms">
700
- <path
701
- data-testid="lang-pie"
702
- size="${percentage}"
703
- d="M ${centerX} ${centerY} L ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y} Z"
704
- fill="${langColor}"
705
- />
706
- </g>
707
- `);
609
+ paths.push(
610
+ el(
611
+ 'g',
612
+ { class: 'stagger', style: `animation-delay: ${delay}ms` },
613
+ el('path', {
614
+ 'data-testid': 'lang-pie',
615
+ size: percentage,
616
+ d: `M ${centerX} ${centerY} L ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y} Z`,
617
+ fill: langColor,
618
+ }),
619
+ ),
620
+ );
708
621
 
709
622
  // Update the start angle for the next part
710
623
  startAngle = endAngle;
@@ -712,35 +625,31 @@ const renderPieLayout = (
712
625
  startDelayCoefficient += 1;
713
626
  }
714
627
 
715
- return `
716
- <svg data-testid="lang-items">
717
- <g transform="translate(0, 0)">
718
- <svg data-testid="pie">
719
- ${paths.join('')}
720
- </svg>
721
- </g>
722
- <g transform="translate(0, 220)">
723
- <svg data-testid="lang-names" x="${CARD_PADDING}">
724
- ${createLanguageTextNode({
725
- langs,
726
- totalSize: totalLanguageSize,
727
- hideProgress: false,
728
- statsFormat,
729
- hideValues,
730
- })}
731
- </svg>
732
- </g>
733
- </svg>
734
- `;
628
+ return el(
629
+ 'svg',
630
+ { 'data-testid': 'lang-items' },
631
+ el('svg', { 'data-testid': 'pie' }, paths),
632
+ el(
633
+ 'g',
634
+ { transform: 'translate(0, 220)' },
635
+ el(
636
+ 'svg',
637
+ { 'data-testid': 'lang-names', x: CARD_PADDING },
638
+ createLanguageTextNode({
639
+ langs,
640
+ totalSize: totalLanguageSize,
641
+ hideProgress: false,
642
+ statsFormat,
643
+ hideValues,
644
+ }),
645
+ ),
646
+ ),
647
+ );
735
648
  };
736
649
 
737
650
  /**
738
651
  * Creates the SVG paths for the language donut chart.
739
652
  *
740
- * @param cx Donut center x-position.
741
- * @param cy Donut center y-position.
742
- * @param radius Donut arc Radius.
743
- * @param percentages Array with donut section percentages.
744
653
  * @returns Array of svg path elements
745
654
  */
746
655
  const createDonutPaths = (
@@ -754,7 +663,7 @@ const createDonutPaths = (
754
663
 
755
664
  const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0);
756
665
  for (const rawPercent of percentages) {
757
- const percent = parseFloat(((rawPercent / totalPercent) * 100).toFixed(2));
666
+ const percent = Number.parseFloat(((rawPercent / totalPercent) * 100).toFixed(2));
758
667
 
759
668
  const endAngle = 3.6 * percent + startAngle;
760
669
  const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise.
@@ -774,11 +683,6 @@ const createDonutPaths = (
774
683
  /**
775
684
  * Renders the donut language card layout.
776
685
  *
777
- * @param langs Array of programming languages.
778
- * @param width Card width.
779
- * @param totalLanguageSize Total size of all languages.
780
- * @param statsFormat Stats format.
781
- * @param hideValues Whether to hide stats values.
782
686
  * @returns Donut layout card SVG object.
783
687
  */
784
688
  const renderDonutLayout = (
@@ -787,9 +691,9 @@ const renderDonutLayout = (
787
691
  totalLanguageSize: number,
788
692
  statsFormat: string,
789
693
  hideValues?: boolean,
790
- ): string => {
694
+ ): Array<Child> => {
791
695
  if (!Number.isFinite(width)) {
792
- throw new Error(`Invalid width: "${width}"`);
696
+ throw new TypeError(`Invalid width: "${width}"`);
793
697
  }
794
698
 
795
699
  const centerX = width / 3;
@@ -797,59 +701,55 @@ const renderDonutLayout = (
797
701
  const radius = centerX - 60;
798
702
  const strokeWidth = 12;
799
703
 
800
- const colors = langs.map(resolveLangColor);
704
+ const colors = langs.map((lang) => resolveLangColor(lang));
801
705
  const langsPercents = langs.map((lang) =>
802
- parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)),
706
+ Number.parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)),
803
707
  );
804
708
 
805
709
  const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents);
806
710
 
807
- const donutPaths =
711
+ const donutPaths: Array<Child> =
808
712
  langs.length === 1
809
- ? `<circle cx="${centerX}" cy="${centerY}" r="${radius}" stroke="${colors[0] ?? DEFAULT_LANG_COLOR}" fill="none" stroke-width="${strokeWidth}" data-testid="lang-donut" size="100"/>`
810
- : langPaths
811
- .map((section, index) => {
812
- const staggerDelay = (index + 3) * 100;
813
- const delay = staggerDelay + 300;
814
-
815
- const output = `
816
- <g class="stagger" style="animation-delay: ${delay}ms">
817
- <path
818
- data-testid="lang-donut"
819
- size="${section.percent}"
820
- d="${section.d}"
821
- stroke="${colors[index] ?? DEFAULT_LANG_COLOR}"
822
- fill="none"
823
- stroke-width="${strokeWidth}">
824
- </path>
825
- </g>
826
- `;
827
-
828
- return output;
829
- })
830
- .join('');
831
-
832
- const donut = `<svg width="${width}" height="${width}">${donutPaths}</svg>`;
833
-
834
- return `
835
- <g transform="translate(0, 0)">
836
- <g transform="translate(0, 0)">
837
- ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, hideValues, statsFormat })}
838
- </g>
839
-
840
- <g transform="translate(125, ${donutCenterTranslation(langs.length)})">
841
- ${donut}
842
- </g>
843
- </g>
844
- `;
713
+ ? [
714
+ el('circle', {
715
+ cx: centerX,
716
+ cy: centerY,
717
+ r: radius,
718
+ stroke: colors[0] ?? DEFAULT_LANG_COLOR,
719
+ fill: 'none',
720
+ 'stroke-width': strokeWidth,
721
+ 'data-testid': 'lang-donut',
722
+ size: 100,
723
+ }),
724
+ ]
725
+ : langPaths.map((section, index) =>
726
+ el(
727
+ 'g',
728
+ { class: 'stagger', style: `animation-delay: ${(index + 3) * 100 + 300}ms` },
729
+ el('path', {
730
+ 'data-testid': 'lang-donut',
731
+ size: section.percent,
732
+ d: section.d,
733
+ stroke: colors[index] ?? DEFAULT_LANG_COLOR,
734
+ fill: 'none',
735
+ 'stroke-width': strokeWidth,
736
+ }),
737
+ ),
738
+ );
739
+
740
+ return [
741
+ createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, hideValues, statsFormat }),
742
+ el(
743
+ 'g',
744
+ { transform: `translate(125, ${donutCenterTranslation(langs.length)})` },
745
+ el('svg', { width, height: width }, donutPaths),
746
+ ),
747
+ ];
845
748
  };
846
749
 
847
750
  /**
848
751
  * Creates the no languages data SVG node.
849
752
  *
850
- * @param props Object with function properties.
851
- * @param props.text No languages data translated text.
852
- * @param props.layout Card layout.
853
753
  * @returns No languages data SVG node string.
854
754
  */
855
755
  const noLanguagesDataNode = ({
@@ -858,20 +758,20 @@ const noLanguagesDataNode = ({
858
758
  }: {
859
759
  text: string;
860
760
  layout: TopLangLayout | undefined;
861
- }): string => {
862
- return `
863
- <text x="${
864
- layout === 'pie' || layout === 'donut-vertical' ? CARD_PADDING : 0
865
- }" y="11" class="stat bold">${encodeHTML(text)}</text>
866
- `;
867
- };
761
+ }): MarkupElement =>
762
+ el(
763
+ 'text',
764
+ {
765
+ x: layout === 'pie' || layout === 'donut-vertical' ? CARD_PADDING : 0,
766
+ y: 11,
767
+ class: 'stat bold',
768
+ },
769
+ text,
770
+ );
868
771
 
869
772
  /**
870
773
  * Get default languages count for provided card layout.
871
774
  *
872
- * @param props Function properties.
873
- * @param props.layout Input layout string.
874
- * @param props.hide_progress Input hide_progress parameter value.
875
775
  * @returns Default languages count for input layout.
876
776
  */
877
777
  const getDefaultLanguagesCountByLayout = ({
@@ -883,22 +783,22 @@ const getDefaultLanguagesCountByLayout = ({
883
783
  }): number => {
884
784
  if (layout === 'compact' || hide_progress === true) {
885
785
  return COMPACT_LAYOUT_DEFAULT_LANGS_COUNT;
886
- } else if (layout === 'donut') {
786
+ }
787
+ if (layout === 'donut') {
887
788
  return DONUT_LAYOUT_DEFAULT_LANGS_COUNT;
888
- } else if (layout === 'donut-vertical') {
789
+ }
790
+ if (layout === 'donut-vertical') {
889
791
  return DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT;
890
- } else if (layout === 'pie') {
792
+ }
793
+ if (layout === 'pie') {
891
794
  return PIE_LAYOUT_DEFAULT_LANGS_COUNT;
892
- } else {
893
- return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT;
894
795
  }
796
+ return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT;
895
797
  };
896
798
 
897
799
  /**
898
800
  * Renders card that display user's most frequently used programming languages.
899
801
  *
900
- * @param topLangs User's most frequently used programming languages.
901
- * @param options Card options.
902
802
  * @returns Language card SVG object.
903
803
  */
904
804
  const renderTopLanguages = (
@@ -929,7 +829,7 @@ const renderTopLanguages = (
929
829
  const { langs, totalLanguageSize } = trimTopLanguages(topLangs, langs_count, hide);
930
830
 
931
831
  let width = card_width
932
- ? isNaN(card_width)
832
+ ? Number.isNaN(card_width)
933
833
  ? DEFAULT_CARD_WIDTH
934
834
  : card_width < MIN_CARD_WIDTH
935
835
  ? MIN_CARD_WIDTH
@@ -939,7 +839,7 @@ const renderTopLanguages = (
939
839
 
940
840
  const { lightColors, darkColors } = getLightDarkColors(options);
941
841
 
942
- let finalLayout: string;
842
+ let finalLayout: Child;
943
843
  if (langs.length === 0) {
944
844
  height = COMPACT_LAYOUT_BASE_HEIGHT;
945
845
  finalLayout = noLanguagesDataNode({
@@ -965,7 +865,7 @@ const renderTopLanguages = (
965
865
  );
966
866
  } else if (layout === 'donut') {
967
867
  height = calculateDonutLayoutHeight(langs.length);
968
- width = width + 50; // padding
868
+ width += 50; // padding
969
869
  finalLayout = renderDonutLayout(langs, width, totalLanguageSize, stats_format, hide_values);
970
870
  } else {
971
871
  finalLayout = renderNormalLayout(langs, width, totalLanguageSize, stats_format, hide_values);
@@ -987,52 +887,40 @@ const renderTopLanguages = (
987
887
  card.setHideBorder(hide_border);
988
888
  card.setHideTitle(hide_title);
989
889
  card.setCSS({
990
- light: ({ textColor, progBarBgColor }) => `
991
- @keyframes slideInAnimation {
992
- from {
993
- width: 0;
994
- }
995
- to {
996
- width: calc(100%-100px);
997
- }
998
- }
999
- @keyframes growWidthAnimation {
1000
- from {
1001
- width: 0;
1002
- }
1003
- to {
1004
- width: 100%;
1005
- }
1006
- }
1007
- .stat {
1008
- font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
1009
- }
1010
- @supports(-moz-appearance: auto) {
1011
- /* Selector detects Firefox */
1012
- .stat { font-size:12px; }
1013
- }
1014
- .bold { font-weight: 700 }
1015
- .lang-name {
1016
- font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
1017
- fill: ${textColor};
1018
- }
1019
- .stagger {
1020
- opacity: 0;
1021
- animation: fadeInAnimation 0.3s ease-in-out forwards;
1022
- }
1023
- #rect-mask rect{
1024
- animation: slideInAnimation 1s ease-in-out forwards;
1025
- }
1026
- .lang-progress{
1027
- animation: growWidthAnimation 0.6s ease-in-out forwards;
1028
- }
1029
- .progress-background { fill: ${progBarBgColor}; }
1030
- `,
1031
- dark: ({ textColor, progBarBgColor }) => `
1032
- .stat { fill: ${textColor}; }
1033
- .lang-name { fill: ${textColor}; }
1034
- .progress-background { fill: ${progBarBgColor}; }
1035
- `,
890
+ light: ({ textColor, progBarBgColor }) => [
891
+ atRule(
892
+ '@keyframes slideInAnimation',
893
+ rule('from', { width: 0 }),
894
+ // Invalid on purpose — `calc` needs spaces around its `-`. Browsers drop the
895
+ // declaration and animate to the rect's own width, which is the reveal the mask wants.
896
+ rule('to', { width: 'calc(100%-100px)' }),
897
+ ),
898
+ atRule(
899
+ '@keyframes growWidthAnimation',
900
+ rule('from', { width: 0 }),
901
+ rule('to', { width: '100%' }),
902
+ ),
903
+ rule('.stat', {
904
+ font: `600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif`,
905
+ fill: textColor,
906
+ }),
907
+ atRule(
908
+ '@supports(-moz-appearance: auto)',
909
+ cssComment('Selector detects Firefox'),
910
+ rule('.stat', { 'font-size': '12px' }),
911
+ ),
912
+ rule('.bold', { 'font-weight': 700 }),
913
+ rule('.lang-name', { font: '400 11px "Segoe UI", Ubuntu, Sans-Serif', fill: textColor }),
914
+ rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
915
+ rule('#rect-mask rect', { animation: 'slideInAnimation 1s ease-in-out forwards' }),
916
+ rule('.lang-progress', { animation: 'growWidthAnimation 0.6s ease-in-out forwards' }),
917
+ rule('.progress-background', { fill: progBarBgColor }),
918
+ ],
919
+ dark: ({ textColor, progBarBgColor }) => [
920
+ rule('.stat', { fill: textColor }),
921
+ rule('.lang-name', { fill: textColor }),
922
+ rule('.progress-background', { fill: progBarBgColor }),
923
+ ],
1036
924
  });
1037
925
 
1038
926
  // `role="img"` hides the inner text from assistive tech, so everything the card
@@ -1048,11 +936,7 @@ const renderTopLanguages = (
1048
936
  return card.render(finalLayout);
1049
937
  }
1050
938
 
1051
- return card.render(`
1052
- <svg data-testid="lang-items" x="${CARD_PADDING}">
1053
- ${finalLayout}
1054
- </svg>
1055
- `);
939
+ return card.render(el('svg', { 'data-testid': 'lang-items', x: CARD_PADDING }, finalLayout));
1056
940
  };
1057
941
 
1058
942
  export {