@kiva/kv-components 3.106.0 → 3.107.1

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 (179) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/CHANGELOG.md +22 -0
  3. package/dist/components/.storybook/main.js +85 -0
  4. package/dist/components/.storybook/package.json +3 -0
  5. package/dist/components/.storybook/preview.js +61 -0
  6. package/dist/components/.storybook/tailwind.css +5 -0
  7. package/dist/components/KvAccordionItem.vue +130 -0
  8. package/dist/components/KvActivityRow.vue +33 -0
  9. package/dist/components/KvBorrowerImage.vue +179 -0
  10. package/dist/components/KvButton.vue +287 -0
  11. package/dist/components/KvCarousel.vue +297 -0
  12. package/dist/components/KvCartModal.vue +365 -0
  13. package/dist/components/KvCheckbox.vue +203 -0
  14. package/dist/components/KvChip.vue +54 -0
  15. package/dist/components/KvClassicLoanCard.vue +527 -0
  16. package/dist/components/KvCommentsAdd.vue +135 -0
  17. package/dist/components/KvCommentsContainer.vue +84 -0
  18. package/dist/components/KvCommentsHeartButton.vue +70 -0
  19. package/dist/components/KvCommentsList.vue +68 -0
  20. package/dist/components/KvCommentsListItem.vue +241 -0
  21. package/dist/components/KvCommentsReplyButton.vue +52 -0
  22. package/dist/components/KvContentfulImg.vue +273 -0
  23. package/dist/components/KvCountdownTimer.vue +59 -0
  24. package/dist/components/KvExpandable.vue +84 -0
  25. package/dist/components/KvExpandableQuestion.vue +120 -0
  26. package/dist/components/KvFlag.vue +120 -0
  27. package/dist/components/KvGrid.vue +28 -0
  28. package/dist/components/KvImpactDashboardHeader.vue +40 -0
  29. package/dist/components/KvInlineActivityCard.vue +55 -0
  30. package/dist/components/KvInlineActivityFeed.vue +38 -0
  31. package/dist/components/KvIntroductionLoanCard.vue +446 -0
  32. package/dist/components/KvLendAmountButton.vue +65 -0
  33. package/dist/components/KvLendCta.vue +451 -0
  34. package/dist/components/KvLightbox.vue +334 -0
  35. package/dist/components/KvLineGraph.vue +128 -0
  36. package/dist/components/KvLoadingPlaceholder.vue +38 -0
  37. package/dist/components/KvLoadingSpinner.vue +81 -0
  38. package/dist/components/KvLoanActivities.vue +268 -0
  39. package/dist/components/KvLoanBookmark.vue +39 -0
  40. package/dist/components/KvLoanCallouts.vue +53 -0
  41. package/dist/components/KvLoanProgressGroup.vue +76 -0
  42. package/dist/components/KvLoanTag.vue +88 -0
  43. package/dist/components/KvLoanTeamPick.vue +44 -0
  44. package/dist/components/KvLoanUse.vue +92 -0
  45. package/dist/components/KvMap.vue +599 -0
  46. package/dist/components/KvMaterialIcon.vue +47 -0
  47. package/dist/components/KvPageContainer.vue +15 -0
  48. package/dist/components/KvPagination.vue +198 -0
  49. package/dist/components/KvPieChart.vue +257 -0
  50. package/dist/components/KvPopper.vue +178 -0
  51. package/dist/components/KvProgressBar.vue +149 -0
  52. package/dist/components/KvRadio.vue +198 -0
  53. package/dist/components/KvSelect.vue +114 -0
  54. package/dist/components/KvSideSheet.vue +134 -0
  55. package/dist/components/KvSwitch.vue +143 -0
  56. package/dist/components/KvTab.vue +90 -0
  57. package/dist/components/KvTabPanel.vue +64 -0
  58. package/dist/components/KvTabs.vue +182 -0
  59. package/dist/components/KvTextInput.vue +247 -0
  60. package/dist/components/KvTextLink.vue +138 -0
  61. package/dist/components/KvThemeProvider.vue +122 -0
  62. package/dist/components/KvToast.vue +221 -0
  63. package/dist/components/KvTooltip.vue +168 -0
  64. package/dist/components/KvTreeMapChart.vue +229 -0
  65. package/dist/components/KvUserAvatar.vue +132 -0
  66. package/dist/components/KvVerticalCarousel.vue +156 -0
  67. package/dist/components/KvVotingCard.vue +160 -0
  68. package/dist/components/KvVotingCardV2.vue +154 -0
  69. package/dist/components/KvWideLoanCard.vue +432 -0
  70. package/dist/components/stories/Forms.stories.js +62 -0
  71. package/dist/components/stories/KvAccordionItem.stories.js +24 -0
  72. package/dist/components/stories/KvActivityRow.stories.js +25 -0
  73. package/dist/components/stories/KvBorrowerImage.stories.js +68 -0
  74. package/dist/components/stories/KvButton.stories.js +144 -0
  75. package/dist/components/stories/KvCarousel.stories.js +426 -0
  76. package/dist/components/stories/KvCartModal.stories.js +54 -0
  77. package/dist/components/stories/KvCheckbox.stories.js +163 -0
  78. package/dist/components/stories/KvChip.stories.js +43 -0
  79. package/dist/components/stories/KvClassicLoanCard.stories.js +480 -0
  80. package/dist/components/stories/KvCommentsAdd.stories.js +32 -0
  81. package/dist/components/stories/KvCommentsContainer.stories.js +42 -0
  82. package/dist/components/stories/KvCommentsHeartButton.stories.js +25 -0
  83. package/dist/components/stories/KvCommentsList.stories.js +39 -0
  84. package/dist/components/stories/KvCommentsListItem.stories.js +45 -0
  85. package/dist/components/stories/KvCommentsReplyButton.stories.js +21 -0
  86. package/dist/components/stories/KvContentfulImg.stories.js +196 -0
  87. package/dist/components/stories/KvCountdownTimer.stories.js +30 -0
  88. package/dist/components/stories/KvExpandableQuestion.stories.js +129 -0
  89. package/dist/components/stories/KvFlag.stories.js +36 -0
  90. package/dist/components/stories/KvGrid.stories.js +97 -0
  91. package/dist/components/stories/KvImpactDashboardHeader.stories.js +22 -0
  92. package/dist/components/stories/KvInlineActivityCard.stories.js +69 -0
  93. package/dist/components/stories/KvInlineActivityFeed.stories.js +76 -0
  94. package/dist/components/stories/KvIntroductionLoanCard.stories.js +208 -0
  95. package/dist/components/stories/KvLendAmountButton.stories.js +31 -0
  96. package/dist/components/stories/KvLendCta.stories.js +177 -0
  97. package/dist/components/stories/KvLightbox.stories.js +304 -0
  98. package/dist/components/stories/KvLineGraph.stories.js +52 -0
  99. package/dist/components/stories/KvLoadingPlaceholder.stories.js +17 -0
  100. package/dist/components/stories/KvLoadingSpinner.stories.js +52 -0
  101. package/dist/components/stories/KvLoanActivities.stories.js +104 -0
  102. package/dist/components/stories/KvLoanBookmark.stories.js +22 -0
  103. package/dist/components/stories/KvLoanCallouts.stories.js +22 -0
  104. package/dist/components/stories/KvLoanProgressGroup.stories.js +29 -0
  105. package/dist/components/stories/KvLoanTag.stories.js +61 -0
  106. package/dist/components/stories/KvLoanTeamPick.stories.js +20 -0
  107. package/dist/components/stories/KvLoanUse.stories.js +60 -0
  108. package/dist/components/stories/KvMap.stories.js +121 -0
  109. package/dist/components/stories/KvMaterialIcon.stories.js +201 -0
  110. package/dist/components/stories/KvPageContainer.stories.js +50 -0
  111. package/dist/components/stories/KvPagination.stories.js +70 -0
  112. package/dist/components/stories/KvPieChart.stories.js +47 -0
  113. package/dist/components/stories/KvProgressBar.stories.js +53 -0
  114. package/dist/components/stories/KvRadio.stories.js +140 -0
  115. package/dist/components/stories/KvSelect.stories.js +125 -0
  116. package/dist/components/stories/KvSideSheet.stories.js +50 -0
  117. package/dist/components/stories/KvSwitch.stories.js +66 -0
  118. package/dist/components/stories/KvTabs.stories.js +106 -0
  119. package/dist/components/stories/KvTextInput.stories.js +194 -0
  120. package/dist/components/stories/KvTextLink.stories.js +55 -0
  121. package/dist/components/stories/KvThemeProvider.stories.js +178 -0
  122. package/dist/components/stories/KvToast.stories.js +117 -0
  123. package/dist/components/stories/KvTooltip.stories.js +26 -0
  124. package/dist/components/stories/KvTreeMapChart.stories.js +42 -0
  125. package/dist/components/stories/KvUserAvatar.stories.js +47 -0
  126. package/dist/components/stories/KvVerticalCarousel.stories.js +168 -0
  127. package/dist/components/stories/KvVotingCard.stories.js +33 -0
  128. package/dist/components/stories/KvVotingCardV2.stories.js +89 -0
  129. package/dist/components/stories/KvWideLoanCard.stories.js +292 -0
  130. package/dist/components/stories/StyleguidePrimitives.stories.js +499 -0
  131. package/dist/components/stories/StyleguideProse.stories.js +215 -0
  132. package/dist/data/countries-borders.json +1 -0
  133. package/dist/data/ne_110m_admin_0_countries.json +1 -0
  134. package/dist/utils/Alea.cjs +87 -0
  135. package/dist/utils/Alea.js +9 -0
  136. package/dist/utils/attrs.cjs +50 -0
  137. package/dist/utils/attrs.js +7 -0
  138. package/dist/utils/carousels.cjs +184 -0
  139. package/dist/utils/carousels.js +8 -0
  140. package/dist/utils/chunk-3HK4G4NT.js +27 -0
  141. package/dist/utils/chunk-55HF2ORX.js +201 -0
  142. package/dist/utils/chunk-AY3PR5S4.js +54 -0
  143. package/dist/utils/chunk-AZPWOFD5.js +148 -0
  144. package/dist/utils/chunk-B5J5WLAH.js +18 -0
  145. package/dist/utils/chunk-GPSH6OPA.js +64 -0
  146. package/dist/utils/chunk-HIY5IW65.js +28 -0
  147. package/dist/utils/chunk-HV3AUBFT.js +15 -0
  148. package/dist/utils/chunk-MSMZIN54.js +110 -0
  149. package/dist/utils/chunk-OXJCCNNW.js +30 -0
  150. package/dist/utils/chunk-S3MABILA.js +22 -0
  151. package/dist/utils/chunk-VIGEMAKO.js +249 -0
  152. package/dist/utils/chunk-YCNMJ4YV.js +37 -0
  153. package/dist/utils/chunk-YFEC5ODJ.js +129 -0
  154. package/dist/utils/expander.cjs +78 -0
  155. package/dist/utils/expander.js +9 -0
  156. package/dist/utils/imageUtils.cjs +54 -0
  157. package/dist/utils/imageUtils.js +9 -0
  158. package/dist/utils/index.cjs +1118 -0
  159. package/dist/utils/index.js +166 -0
  160. package/dist/utils/loanCard.cjs +222 -0
  161. package/dist/utils/loanCard.js +9 -0
  162. package/dist/utils/loanUtils.cjs +170 -0
  163. package/dist/utils/loanUtils.js +23 -0
  164. package/dist/utils/mapUtils.cjs +276 -0
  165. package/dist/utils/mapUtils.js +15 -0
  166. package/dist/utils/printing.cjs +42 -0
  167. package/dist/utils/printing.js +9 -0
  168. package/dist/utils/scrollLock.cjs +54 -0
  169. package/dist/utils/scrollLock.js +13 -0
  170. package/dist/utils/throttle.cjs +38 -0
  171. package/dist/utils/throttle.js +7 -0
  172. package/dist/utils/touchEvents.cjs +47 -0
  173. package/dist/utils/touchEvents.js +11 -0
  174. package/dist/utils/treemap.cjs +133 -0
  175. package/dist/utils/treemap.js +7 -0
  176. package/package.json +12 -4
  177. package/utils/index.js +14 -0
  178. package/vue/KvVerticalCarousel.vue +1 -1
  179. package/index.js +0 -3
@@ -0,0 +1,92 @@
1
+ <!-- eslint-disable vue/no-v-html -->
2
+ <template>
3
+ <p
4
+ class="tw-line-clamp-4"
5
+ v-html="loanUse"
6
+ ></p>
7
+ </template>
8
+
9
+ <script>
10
+ import numeral from 'numeral';
11
+
12
+ const DIRECT = 'direct';
13
+
14
+ export default {
15
+ name: 'KvLoanUse',
16
+ props: {
17
+ anonymizationLevel: {
18
+ type: String,
19
+ default: 'none',
20
+ },
21
+ use: {
22
+ type: String,
23
+ default: '',
24
+ },
25
+ loanAmount: {
26
+ type: String,
27
+ default: '',
28
+ },
29
+ status: {
30
+ type: String,
31
+ default: '',
32
+ },
33
+ borrowerCount: {
34
+ type: Number,
35
+ default: 1,
36
+ },
37
+ name: {
38
+ type: String,
39
+ default: '',
40
+ },
41
+ distributionModel: {
42
+ type: String,
43
+ default: DIRECT,
44
+ },
45
+ whySpecial: {
46
+ type: String,
47
+ default: '',
48
+ },
49
+ hideLoanAmount: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ },
54
+ computed: {
55
+ helpLanguage() {
56
+ if (this.status === 'fundraising' || this.status === 'inactive' || this.status === 'reviewed') {
57
+ return 'helps';
58
+ }
59
+ return 'helped';
60
+ },
61
+ isDirect() {
62
+ return this.distributionModel === DIRECT;
63
+ },
64
+ whySpecialSentence() {
65
+ return this.whySpecial
66
+ ? ` This loan is special because ${this.whySpecial.charAt(0).toLowerCase() + this.whySpecial.slice(1)}`
67
+ : '';
68
+ },
69
+ loanUse() {
70
+ if (this.anonymizationLevel === 'full' || this.use.length === 0) {
71
+ return 'For the borrower\'s privacy, this loan has been made anonymous.';
72
+ }
73
+
74
+ if (this.hideLoanAmount) {
75
+ return `Help <span class="data-hj-suppress">${this.name}</span> `
76
+ + `${this.use.charAt(0).toLowerCase() + this.use.slice(1)} `
77
+ + `${this.whySpecialSentence}`;
78
+ }
79
+
80
+ const isGroup = this.borrowerCount > 1;
81
+
82
+ return `${numeral(this.loanAmount).format('$0,0')} `
83
+ + `${this.isDirect ? 'to' : this.helpLanguage} `
84
+ + `${isGroup ? 'a member of ' : ''}`
85
+ + `<span class="data-hj-suppress">${this.name}</span> `
86
+ + `${this.isDirect ? `${this.helpLanguage} ` : ''}`
87
+ + `${this.use.charAt(0).toLowerCase() + this.use.slice(1)}`
88
+ + `${this.whySpecialSentence}`;
89
+ },
90
+ },
91
+ };
92
+ </script>
@@ -0,0 +1,599 @@
1
+ <template>
2
+ <div
3
+ class="tw-relative tw-block tw-w-full"
4
+ :style="mapDimensions"
5
+ >
6
+ <div
7
+ :id="`kv-map-holder-${mapId}`"
8
+ :ref="refString"
9
+ class="tw-w-full tw-h-full tw-bg-black"
10
+ :style="{ position: 'absolute' }"
11
+ ></div>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import kvTokensPrimitives from '@kiva/kv-tokens/primitives.json';
17
+ import { animationCoordinator, generateMapMarkers, getCountryColor } from '../utils/mapUtils';
18
+
19
+ export default {
20
+ name: 'KvMap',
21
+ props: {
22
+ /**
23
+ * Aspect Ration for computed map dimensions
24
+ * We'll divide the container width by this to determine the height
25
+ */
26
+ aspectRatio: {
27
+ type: Number,
28
+ default: 1,
29
+ },
30
+ /**
31
+ * Control how quickly the autoZoom occurs
32
+ */
33
+ autoZoomDelay: {
34
+ type: Number,
35
+ default: 1500,
36
+ },
37
+ /**
38
+ * Set the height to override aspect ratio driven and/or default dimensions
39
+ */
40
+ height: {
41
+ type: Number,
42
+ default: null,
43
+ },
44
+ /**
45
+ * Setting this initialZoom will zoom the map from initialZoom to zoom when the map enters the viewport
46
+ */
47
+ initialZoom: {
48
+ type: Number,
49
+ default: null,
50
+ },
51
+ /**
52
+ * Set the center point latitude
53
+ */
54
+ lat: {
55
+ type: Number,
56
+ default: null,
57
+ },
58
+ /**
59
+ * Set the center point longitude
60
+ */
61
+ long: {
62
+ type: Number,
63
+ default: null,
64
+ },
65
+ /**
66
+ * Set this if there are more than one map on the page
67
+ */
68
+ mapId: {
69
+ type: Number,
70
+ default: 0,
71
+ },
72
+ /**
73
+ * Force use of Leaflet
74
+ */
75
+ useLeaflet: {
76
+ type: Boolean,
77
+ default: false,
78
+ },
79
+ /**
80
+ * Set the width to override aspect ratio driven and/or default dimensions
81
+ */
82
+ width: {
83
+ type: Number,
84
+ default: null,
85
+ },
86
+ /**
87
+ * Default zoom level
88
+ */
89
+ zoomLevel: {
90
+ type: Number,
91
+ default: 4,
92
+ },
93
+ /**
94
+ * Borrower points object.
95
+ * If this object is present, the advanced animation will be triggered
96
+ * Sample object:
97
+ * {
98
+ borrowerPoints: [
99
+ {
100
+ image: 'https://www-kiva-org.freetls.fastly.net/img/w80h80fz50/e60a3d61ff052d60991c5d6bbf4a45d3.jpg',
101
+ location: [-77.032, 38.913],
102
+ },
103
+ {
104
+ image: 'https://www-kiva-org.freetls.fastly.net/img/w80h80fz50/6101929097c6e5de48232a4d1ae3b71c.jpg',
105
+ location: [41.402, 7.160],
106
+ },
107
+ {
108
+ image: 'https://www-kiva-org.freetls.fastly.net/img/w80h80fz50/11e018ee3d8b9c5adee459c16a29d264.jpg',
109
+ location: [-73.356596, 3.501],
110
+ },
111
+ ],
112
+ * }
113
+ */
114
+ advancedAnimation: {
115
+ type: Object,
116
+ required: false,
117
+ default: () => ({}),
118
+ },
119
+ /**
120
+ * Show the zoom control
121
+ */
122
+ showZoomControl: {
123
+ type: Boolean,
124
+ default: false,
125
+ },
126
+ /**
127
+ * Allow dragging of the map
128
+ */
129
+ allowDragging: {
130
+ type: Boolean,
131
+ default: false,
132
+ },
133
+ /**
134
+ * Show labels on the map
135
+ * Working for leaflet only
136
+ */
137
+ showLabels: {
138
+ type: Boolean,
139
+ default: true,
140
+ },
141
+ /**
142
+ * Lender data for the map
143
+ * Working for leaflet only
144
+ */
145
+ countriesData: {
146
+ type: Array,
147
+ default: () => ([]),
148
+ },
149
+ /**
150
+ * Show fundraising loans
151
+ * Working for leaflet only
152
+ */
153
+ showFundraisingLoans: {
154
+ type: Boolean,
155
+ default: false,
156
+ },
157
+ },
158
+ data() {
159
+ return {
160
+ hasWebGL: false,
161
+ leafletReady: false,
162
+ mapInstance: null,
163
+ mapLibreReady: false,
164
+ mapLoaded: false,
165
+ zoomActive: false,
166
+ countriesBorders: {},
167
+ };
168
+ },
169
+ computed: {
170
+ mapDimensions() {
171
+ // Use container to derive height based on aspect ration + width
172
+ const container = this.$el?.getBoundingClientRect();
173
+ const height = container ? `${container.width / this.aspectRatio}px` : '300px';
174
+ const width = container ? `${container.width}px` : '100%';
175
+ // Override values if deliberate height or width are provided
176
+ return {
177
+ height: this.height ? `${this.height}px` : height,
178
+ width: this.width ? `${this.width}px` : width,
179
+ paddingBottom: this.height ? `${this.height}px` : `${100 / this.aspectRatio}%`,
180
+ };
181
+ },
182
+ refString() {
183
+ return `mapholder${this.mapId}`;
184
+ },
185
+ },
186
+ watch: {
187
+ lat(next, prev) {
188
+ if (prev === null && this.long && !this.mapLibreReady && !this.leafletReady) {
189
+ this.initializeMap();
190
+ }
191
+ },
192
+ long(next, prev) {
193
+ if (prev === null && this.lat && !this.mapLibreReady && !this.leafletReady) {
194
+ this.initializeMap();
195
+ }
196
+ },
197
+ showFundraisingLoans() {
198
+ if (this.mapInstance) {
199
+ this.mapInstance.remove();
200
+ this.initializeLeaflet();
201
+ }
202
+ },
203
+ },
204
+ async mounted() {
205
+ if (this.countriesData) {
206
+ // current source data is from https://geojson.xyz/ under "admin 0 countries"
207
+ // https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_admin_0_countries.geojson
208
+ this.countriesBorders = await import('../data/ne_110m_admin_0_countries.json');
209
+ }
210
+
211
+ if (!this.mapLibreReady && !this.leafletReady) {
212
+ this.initializeMap();
213
+ }
214
+ },
215
+ beforeDestroy() {
216
+ if (this.mapInstance) {
217
+ if (!this.hasWebGL && !this.leafletReady) {
218
+ // turn off the leaflet instance
219
+ this.mapInstance.off();
220
+ }
221
+ // remove either leaflet or maplibregl
222
+ this.mapInstance.remove();
223
+ }
224
+ this.destroyWrapperObserver();
225
+ },
226
+ methods: {
227
+ activateZoom(zoomOut = false) {
228
+ const { mapInstance, hasWebGL, mapLibreReady } = this;
229
+ const currentZoomLevel = mapInstance.getZoom();
230
+ // exit if already zoomed in (getZoom() works for both leaflet + maplibregl)
231
+ if ((!zoomOut && currentZoomLevel === this.zoomLevel)
232
+ || (zoomOut && currentZoomLevel === this.initialZoom)) return false;
233
+
234
+ this.zoomActive = true;
235
+ // establish delayed zoom duration
236
+ const timedZoom = window.setTimeout(() => {
237
+ if (hasWebGL && mapLibreReady) {
238
+ // maplibregl specific zoom method
239
+ mapInstance.zoomTo(
240
+ zoomOut ? this.initialZoom : this.zoomLevel,
241
+ { duration: 1200 },
242
+ );
243
+ } else {
244
+ // leaflet specific zoom method
245
+ mapInstance.setZoom(zoomOut ? this.initialZoom : this.zoomLevel);
246
+ }
247
+
248
+ clearTimeout(timedZoom);
249
+ this.zoomActive = false;
250
+ }, this.autoZoomDelay);
251
+ },
252
+ createWrapperObserver() {
253
+ // Watch for the wrapper element moving in and out of the viewport
254
+ this.wrapperObserver = this.createIntersectionObserver({
255
+ targets: [this.$refs?.[this.refString]],
256
+ callback: (entries) => {
257
+ // only activate autoZoom if we have an initialZoom set
258
+ entries.forEach((entry) => {
259
+ if (entry.target === this.$refs?.[this.refString] && !this.zoomActive) {
260
+ if (entry.intersectionRatio > 0) {
261
+ // activate zoom
262
+ if (this.initialZoom !== null) {
263
+ this.activateZoom();
264
+ }
265
+ // animate map
266
+ if (this.advancedAnimation?.borrowerPoints) {
267
+ this.animateMap();
268
+ }
269
+ }
270
+ }
271
+ });
272
+ },
273
+ });
274
+ },
275
+ destroyWrapperObserver() {
276
+ if (this.wrapperObserver) {
277
+ this.wrapperObserver.disconnect();
278
+ }
279
+ },
280
+ checkWebGL() {
281
+ // exit and use leaflet if specified or document isn't present
282
+ if (this.useLeaflet || typeof document === 'undefined') return false;
283
+ // via. https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/By_example/Detect_WebGL
284
+ // Create canvas element. The canvas is not added to the document itself,
285
+ // so it is never displayed in the browser window.
286
+ const canvas = document.createElement('canvas');
287
+ // Get WebGLRenderingContext from canvas element.
288
+ const gl = canvas.getContext('webgl')
289
+ || canvas.getContext('experimental-webgl');
290
+ // Report the result.
291
+ if (gl && gl instanceof WebGLRenderingContext) {
292
+ this.hasWebGL = true;
293
+ return true;
294
+ }
295
+ return false;
296
+ },
297
+ initializeMap() {
298
+ /**
299
+ * This initial checkWebGL() call kicks off the library asset inclusion
300
+ * We then start polling for the readiness of our selected map library and initialize it once ready
301
+ */
302
+ const mapScript = document.createElement('script');
303
+ const mapStyle = document.createElement('link');
304
+ mapScript.setAttribute('async', true);
305
+ mapScript.setAttribute('defer', true);
306
+ mapStyle.setAttribute('rel', 'stylesheet');
307
+ if (this.checkWebGL()) {
308
+ mapScript.setAttribute('vmid', `maplibregljs${this.mapId}`);
309
+ mapStyle.setAttribute('vmid', `maplibreglcss${this.mapId}`);
310
+ mapScript.setAttribute('src', 'https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js');
311
+ mapStyle.setAttribute('href', 'https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css');
312
+
313
+ this.testDelayedGlobalLibrary('maplibregl').then((response) => {
314
+ if (response.loaded && !this.mapLoaded && !this.useLeaflet && this.lat && this.long) {
315
+ this.initializeMapLibre();
316
+ this.mapLibreReady = true;
317
+ }
318
+ });
319
+ } else {
320
+ mapScript.setAttribute('vmid', `leafletjs${this.mapId}`);
321
+ mapStyle.setAttribute('vmid', `leaftletcss${this.mapId}`);
322
+ mapScript.setAttribute('src', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js');
323
+ mapStyle.setAttribute('href', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css');
324
+
325
+ this.testDelayedGlobalLibrary('L').then((leafletTest) => {
326
+ if (leafletTest.loaded && !this.mapLoaded && this.lat && this.long) {
327
+ this.initializeLeaflet();
328
+ this.leafletReady = true;
329
+ }
330
+ });
331
+ }
332
+ document.head.appendChild(mapScript);
333
+ document.head.appendChild(mapStyle);
334
+ },
335
+ initializeLeaflet() {
336
+ /* eslint-disable no-undef, max-len */
337
+ // Initialize primary mapInstance
338
+ this.mapInstance = L.map(`kv-map-holder-${this.mapId}`, {
339
+ center: [this.lat, this.long],
340
+ zoom: this.initialZoom || this.zoomLevel,
341
+ // todo make props for the following options
342
+ dragging: this.allowDragging,
343
+ zoomControl: this.showZoomControl,
344
+ animate: true,
345
+ scrollWheelZoom: false,
346
+ doubleClickZoom: false,
347
+ attributionControl: false,
348
+ });
349
+ /* eslint-disable quotes */
350
+ // Add our tileset to the mapInstance
351
+ let tileLayer = 'https://api.maptiler.com/maps/landscape/{z}/{x}/{y}.png?key=n1Mz5ziX3k6JfdjFe7mx';
352
+ if (this.showLabels) {
353
+ tileLayer = 'https://api.maptiler.com/maps/bright/{z}/{x}/{y}.png?key=n1Mz5ziX3k6JfdjFe7mx';
354
+ }
355
+ L.tileLayer(tileLayer, {
356
+ tileSize: 512,
357
+ zoomOffset: -1,
358
+ minZoom: 1,
359
+ crossOrigin: true,
360
+ }).addTo(this.mapInstance);
361
+
362
+ if (this.countriesData.length > 0) {
363
+ L.geoJson(
364
+ this.getCountriesData(),
365
+ {
366
+ style: this.countryStyle,
367
+ onEachFeature: this.onEachCountryFeature,
368
+ },
369
+ ).addTo(this.mapInstance);
370
+
371
+ this.countriesData.forEach((country) => {
372
+ if (country.numLoansFundraising > 0 && this.showFundraisingLoans) {
373
+ const circle = L.circle([country.lat, country.long], {
374
+ color: kvTokensPrimitives.colors.black,
375
+ weight: 1,
376
+ fillColor: kvTokensPrimitives.colors.brand[900],
377
+ fillOpacity: 1,
378
+ radius: 130000,
379
+ }).addTo(this.mapInstance);
380
+
381
+ const tooltipText = `Click to see ${country.numLoansFundraising} fundraising loans in ${country.label}`;
382
+ circle.bindTooltip(tooltipText);
383
+
384
+ circle.on('click', () => {
385
+ this.circleMapClicked(country.isoCode);
386
+ });
387
+ }
388
+ });
389
+ }
390
+ /* eslint-enable quotes */
391
+ /* eslint-enable no-undef, max-len */
392
+
393
+ // signify map has loaded
394
+ this.mapLoaded = true;
395
+ // only activate autoZoom if we have an initialZoom set
396
+ if (this.initialZoom !== null) {
397
+ this.createWrapperObserver();
398
+ }
399
+ },
400
+ initializeMapLibre() {
401
+ // Initialize primary mapInstance
402
+ /* eslint-disable no-undef */
403
+ let tileLayer = 'https://api.maptiler.com/maps/landscape/style.json?key=n1Mz5ziX3k6JfdjFe7mx';
404
+ if (this.showLabels) {
405
+ tileLayer = 'https://api.maptiler.com/maps/bright/style.json?key=n1Mz5ziX3k6JfdjFe7mx';
406
+ }
407
+
408
+ this.mapInstance = new maplibregl.Map({
409
+ container: `kv-map-holder-${this.mapId}`,
410
+ style: tileLayer,
411
+ center: [this.long, this.lat],
412
+ zoom: this.initialZoom || this.zoomLevel,
413
+ attributionControl: false,
414
+ dragPan: this.allowDragging,
415
+ scrollZoom: false,
416
+ doubleClickZoom: false,
417
+ dragRotate: false,
418
+ });
419
+
420
+ if (this.showZoomControl) {
421
+ this.mapInstance.addControl(new maplibregl.NavigationControl());
422
+ }
423
+
424
+ this.mapInstance.on('load', () => {
425
+ // signify map has loaded
426
+ this.mapLoaded = true;
427
+ // Create wrapper observer to watch for map entering viewport
428
+ if (this.initialZoom !== null || this.advancedAnimation?.borrowerPoints) {
429
+ this.createWrapperObserver();
430
+ }
431
+ });
432
+ /* eslint-enable no-undef */
433
+ },
434
+ animateMap() {
435
+ // remove country labels
436
+ this.mapInstance.style.stylesheet.layers.forEach((layer) => {
437
+ if (layer.type === 'symbol') {
438
+ this.mapInstance.removeLayer(layer.id);
439
+ }
440
+ });
441
+ // generate map markers for borrower points
442
+ generateMapMarkers(this.mapInstance, this.advancedAnimation.borrowerPoints);
443
+
444
+ // wait 500 ms before calling the animation coordinator promise
445
+ // to allow the map to scroll into view
446
+ setTimeout(() => {
447
+ animationCoordinator(this.mapInstance, this.advancedAnimation.borrowerPoints)
448
+ .then(() => {
449
+ // when animation is complete reset map to component properties
450
+ this.mapInstance.dragPan.enable();
451
+ this.mapInstance.scrollZoom.enable();
452
+ this.mapInstance.scrollZoom.enable();
453
+ this.mapInstance.easeTo({
454
+ center: [this.long, this.lat],
455
+ zoom: this.initialZoom || this.zoomLevel,
456
+ });
457
+ });
458
+ }, 500);
459
+ },
460
+ checkIntersectionObserverSupport() {
461
+ if (typeof window === 'undefined'
462
+ || !('IntersectionObserver' in window)
463
+ || !('IntersectionObserverEntry' in window)
464
+ || !('intersectionRatio' in window.IntersectionObserverEntry.prototype)) {
465
+ return false;
466
+ }
467
+ return true;
468
+ },
469
+ createIntersectionObserver({ callback, options, targets } = {}) {
470
+ if (this.checkIntersectionObserverSupport()) {
471
+ const observer = new IntersectionObserver(callback, options);
472
+ targets.forEach((target) => observer.observe(target));
473
+ return observer;
474
+ }
475
+ },
476
+ testDelayedGlobalLibrary(library, timeout = 3000) {
477
+ // return a promise
478
+ return new Promise((resolve, reject) => {
479
+ if (typeof window === 'undefined') {
480
+ reject(new Error('window object not available'));
481
+ }
482
+ // establish timeout to limit time until promise resolution
483
+ let readyStateTimeout;
484
+ // establish interval to check for library presence
485
+ const readyStateInterval = window.setInterval(() => {
486
+ // determine if library is present on window
487
+ if (typeof window[library] !== 'undefined') {
488
+ // cleanup timers
489
+ clearInterval(readyStateInterval);
490
+ clearTimeout(readyStateTimeout);
491
+ // resolve the promise
492
+ resolve({ loaded: true });
493
+ }
494
+ }, 100);
495
+
496
+ // activate timeout
497
+ readyStateTimeout = window.setTimeout(() => {
498
+ // clean up interval and timeout
499
+ clearInterval(readyStateInterval);
500
+ clearTimeout(readyStateTimeout);
501
+ // resolve the promise
502
+ resolve({ loaded: false });
503
+ }, timeout);
504
+ });
505
+ },
506
+ getCountriesData() {
507
+ const countriesFeatures = this.countriesBorders.features ?? [];
508
+
509
+ countriesFeatures.forEach((country, index) => {
510
+ const countryData = this.countriesData.find((data) => data.isoCode === country.properties.iso_a2);
511
+ if (countryData) {
512
+ countriesFeatures[index].lenderLoans = countryData.value;
513
+ countriesFeatures[index].numLoansFundraising = countryData.numLoansFundraising;
514
+ }
515
+ });
516
+
517
+ return this.countriesBorders;
518
+ },
519
+ countryStyle(feature) {
520
+ return {
521
+ color: kvTokensPrimitives.colors.white,
522
+ fillColor: getCountryColor(feature.lenderLoans, this.countriesData, kvTokensPrimitives),
523
+ weight: 1,
524
+ fillOpacity: 1,
525
+ };
526
+ },
527
+ onEachCountryFeature(feature, layer) {
528
+ const loansString = feature.lenderLoans
529
+ ? `${feature.lenderLoans} loan${feature.lenderLoans > 1 ? 's' : ''}`
530
+ : '0 loans';
531
+ const countryString = `${feature.properties.name} <br/> ${loansString}`;
532
+
533
+ layer.bindTooltip(countryString, {
534
+ sticky: true,
535
+ });
536
+
537
+ layer.on({
538
+ mouseover: this.highlightFeature,
539
+ mouseout: this.resetHighlight,
540
+ });
541
+ },
542
+ highlightFeature(e) {
543
+ const layer = e.target;
544
+
545
+ layer.setStyle({
546
+ fillColor: kvTokensPrimitives.colors.gray[500],
547
+ });
548
+ },
549
+ resetHighlight(e) {
550
+ const layer = e.target;
551
+ const { feature } = layer;
552
+
553
+ layer.setStyle({
554
+ fillColor: getCountryColor(feature.lenderLoans, this.countriesData, kvTokensPrimitives),
555
+ });
556
+ },
557
+ circleMapClicked(countryIso) {
558
+ this.$emit('country-lend-filter', countryIso);
559
+ },
560
+ },
561
+ };
562
+ </script>
563
+
564
+ <style>
565
+ /* Styles for animation map markers defined in @kiva/kv-components/utils/mapAnimation.js */
566
+ .map-marker {
567
+ margin-top: -77px;
568
+ margin-left: 35px;
569
+ display: block;
570
+ border: none;
571
+ border-radius: 50%;
572
+ cursor: pointer;
573
+ padding: 0;
574
+ }
575
+
576
+ .map-marker::after {
577
+ content: '';
578
+ position: absolute;
579
+ top: -8px;
580
+ left: -8px;
581
+ right: -8px;
582
+ bottom: -8px;
583
+ border-radius: 50%;
584
+ border: 4px solid #000;
585
+ }
586
+
587
+ .map-marker::before {
588
+ content: "";
589
+ width: 0;
590
+ height: 0;
591
+ left: -13px;
592
+ bottom: -32px;
593
+ border: 9px solid transparent;
594
+ border-left: 40px solid #000;
595
+ transform: rotate(114deg);
596
+ position: absolute;
597
+ z-index: -1;
598
+ }
599
+ </style>