• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 23950562977

03 Apr 2026 02:56PM UTC coverage: 60.018% (-1.7%) from 61.699%
23950562977

Pull #3271

github

web-flow
Merge 8677ca559 into bc59e880b
Pull Request #3271: chore: deck.gl 9.2 upgrade & loaders.gl, luma.gl upgrades

6517 of 12945 branches covered (50.34%)

Branch coverage included in aggregate %.

310 of 960 new or added lines in 57 files covered. (32.29%)

122 existing lines in 17 files now uncovered.

13299 of 20072 relevant lines covered (66.26%)

78.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

4.9
/src/utils/src/dom-utils.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import Console from 'global/console';
5
import Window from 'global/window';
6
import document from 'global/document';
7
import {IMAGE_EXPORT_ERRORS} from '@kepler.gl/constants';
8

9
export function processClone(original, clone) {
10
  if (!(clone instanceof Window.Element)) {
×
11
    return clone;
×
12
  }
13

14
  function copyProperties(sourceStyle, targetStyle) {
15
    const propertyKeys = asArray(sourceStyle);
×
16
    propertyKeys.forEach(name => {
×
17
      targetStyle.setProperty(
×
18
        name,
19
        sourceStyle.getPropertyValue(name),
20
        sourceStyle.getPropertyPriority(name)
21
      );
22
    });
23
  }
24

25
  function copyStyle(source, target) {
26
    if (source.cssText) {
×
27
      target.cssText = source.cssText;
×
28
      // add additional copy of composite styles
29
      if (source.font) {
×
30
        target.font = source.font;
×
31
      }
32
    } else {
33
      copyProperties(source, target);
×
34
    }
35
  }
36

37
  function cloneStyle(og, cln) {
38
    const originalStyle = Window.getComputedStyle(og);
×
39
    copyStyle(originalStyle, cln.style);
×
40
  }
41

42
  function formatPseudoElementStyle(cln, elm, stl) {
43
    const formatCssText = stl1 => {
×
44
      const cnt = stl1.getPropertyValue('content');
×
45
      return `${stl.cssText} content: ${cnt};`;
×
46
    };
47

48
    const formatProperty = name => {
×
49
      return `${name}:${stl.getPropertyValue(name)}${
×
50
        stl.getPropertyPriority(name) ? ' !important' : ''
×
51
      }`;
52
    };
53

54
    const formatCssProperties = stl2 => {
×
55
      return `${asArray(stl2).map(formatProperty).join('; ')};`;
×
56
    };
57

58
    const selector = `.${cln}:${elm}`;
×
59
    const cssText = stl.cssText ? formatCssText(stl) : formatCssProperties(stl);
×
60

61
    return document.createTextNode(`${selector}{${cssText}}`);
×
62
  }
63

64
  function clonePseudoElement(org, cln, element) {
65
    const style = Window.getComputedStyle(org, element);
×
66
    const content = style.getPropertyValue('content');
×
67

68
    if (content === '' || content === 'none') {
×
69
      return;
×
70
    }
71

72
    const className = uid();
×
73
    cln.className = `${cln.className} ${className}`;
×
74
    const styleElement = document.createElement('style');
×
75
    styleElement.appendChild(formatPseudoElementStyle(className, element, style));
×
76
    cln.appendChild(styleElement);
×
77
  }
78

79
  function clonePseudoElements([og, cln]) {
80
    [':before', ':after'].forEach(element => clonePseudoElement(og, cln, element));
×
81
  }
82

83
  function copyUserInput([og, cln]) {
84
    if (og instanceof Window.HTMLTextAreaElement) cln.innerHTML = og.value;
×
85
    if (og instanceof Window.HTMLInputElement) cln.setAttribute('value', og.value);
×
86
  }
87

88
  function fixSvg(cln) {
89
    if (!(cln instanceof Window.SVGElement)) return;
×
90
    cln.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
×
91

92
    if (!(cln instanceof Window.SVGRectElement)) return;
×
93
    ['width', 'height'].forEach(attribute => {
×
94
      const value = cln.getAttribute(attribute);
×
95
      if (!value) return;
×
96

97
      cln.style.setProperty(attribute, value);
×
98
    });
99
  }
100

101
  return (
×
102
    Promise.resolve([original, clone])
103
      .then(([og, cln]) => {
104
        cloneStyle(og, cln);
×
105
        return [og, cln];
×
106
      })
107
      .then(([og, cln]) => {
108
        clonePseudoElements([og, cln]);
×
109
        return [og, cln];
×
110
      })
111
      .then(([og, cln]) => {
112
        copyUserInput([og, cln]);
×
113
        return [og, cln];
×
114
      })
115
      .then(([og, cln]) => {
116
        fixSvg(cln);
×
117
        return [og, cln];
×
118
      })
119
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
120
      .then(([og, cln]) => cln)
×
121
  );
122
}
123

124
/** **
125
 * UTILS
126
 ****/
127
export function asArray(arrayLike) {
128
  const array: any[] = [];
×
129
  const length = arrayLike.length;
×
130
  for (let i = 0; i < length; i++) array.push(arrayLike[i]);
×
131
  return array;
×
132
}
133

134
export function fourRandomChars() {
135
  return `0000${((Math.random() * Math.pow(36, 4)) << 0).toString(36)}`.slice(-4);
×
136
}
137

138
export function uid() {
139
  let index = 0;
×
140

141
  return `u${fourRandomChars()}${index++}`;
×
142
}
143

144
export function makeImage(uri) {
145
  return new Promise((resolve, reject) => {
×
146
    const image = new Window.Image();
×
147
    image.onload = () => {
×
148
      resolve(image);
×
149
    };
150
    image.onerror = err => {
×
151
      const message = IMAGE_EXPORT_ERRORS.dataUri;
×
152
      Console.log(uri);
×
153
      // error is an Event Object
154
      // https://www.w3schools.com/jsref/obj_event.asp
155
      reject({event: err, message});
×
156
    };
157
    image.src = uri;
×
158
  });
159
}
160

161
export function isDataUrl(url) {
162
  return url.search(/^(data:)/) !== -1;
×
163
}
164

165
function parseExtension(url) {
166
  const match = /\.([^./]*?)$/g.exec(url);
×
167
  if (match) {
×
168
    return match[1];
×
169
  }
170
  return '';
×
171
}
172

173
function mimes() {
174
  /*
175
   * Only WOFF and EOT mime types for fonts are 'real'
176
   * see http://www.iana.org/assignments/media-types/media-types.xhtml
177
   */
178
  const WOFF = 'application/font-woff';
×
179
  const JPEG = 'image/jpeg';
×
180

181
  return {
×
182
    woff: WOFF,
183
    woff2: WOFF,
184
    ttf: 'application/font-truetype',
185
    eot: 'application/vnd.ms-fontobject',
186
    png: 'image/png',
187
    jpg: JPEG,
188
    jpeg: JPEG,
189
    gif: 'image/gif',
190
    tiff: 'image/tiff',
191
    svg: 'image/svg+xml'
192
  };
193
}
194

195
export function mimeType(url) {
196
  const extension = parseExtension(url).toLowerCase();
×
197
  return mimes()[extension] || '';
×
198
}
199

200
export function dataAsUrl(content, type) {
201
  return `data:${type};base64,${content}`;
×
202
}
203

204
export function escape(string) {
205
  return string.replace(/([.*+?^${}()|[\]/\\])/g, '\\$1');
×
206
}
207

208
export function delay(ms) {
UNCOV
209
  return arg => {
×
210
    return new Promise(resolve => {
×
211
      Window.setTimeout(() => {
×
212
        resolve(arg);
×
213
      }, ms);
214
    });
215
  };
216
}
217

218
export function isSrcAsDataUrl(text) {
219
  const DATA_URL_REGEX = /url\(['"]?(data:)([^'"]+?)['"]?\)/;
5✔
220
  return text.search(DATA_URL_REGEX) !== -1;
5✔
221
}
222

223
function cvToBlob(canvas) {
224
  return new Promise(resolve => {
×
225
    const binaryString = Window.atob(canvas.toDataURL().split(',')[1]);
×
226
    const length = binaryString.length;
×
227
    const binaryArray = new Uint8Array(length);
×
228

229
    for (let i = 0; i < length; i++) binaryArray[i] = binaryString.charCodeAt(i);
×
230

231
    resolve(new Window.Blob([binaryArray], {type: 'image/png'}));
×
232
  });
233
}
234

235
export function canvasToBlob(canvas) {
236
  if (canvas.toBlob)
×
237
    return new Promise(resolve => {
×
238
      canvas.toBlob(resolve);
×
239
    });
240

241
  return cvToBlob(canvas);
×
242
}
243

244
export function escapeXhtml(string) {
245
  return string.replace(/#/g, '%23').replace(/\n/g, '%0A');
×
246
}
247

248
export function getWidth(node) {
249
  const leftBorder = px(node, 'border-left-width');
×
250
  const rightBorder = px(node, 'border-right-width');
×
251
  return node.scrollWidth + leftBorder + rightBorder;
×
252
}
253

254
export function getHeight(node) {
255
  const topBorder = px(node, 'border-top-width');
×
256
  const bottomBorder = px(node, 'border-bottom-width');
×
257
  return node.scrollHeight + topBorder + bottomBorder;
×
258
}
259

260
function px(node, styleProperty) {
261
  const value = Window.getComputedStyle(node).getPropertyValue(styleProperty);
×
262
  return parseFloat(value.replace('px', ''));
×
263
}
264

265
export function resolveUrl(url, baseUrl) {
266
  const doc = document.implementation.createHTMLDocument();
×
267
  const base = doc.createElement('base');
×
268
  doc.head.appendChild(base);
×
269
  const a = doc.createElement('a');
×
270
  doc.body.appendChild(a);
×
271
  base.href = baseUrl;
×
272
  a.href = url;
×
273
  return a.href;
×
274
}
275

276
export function getAndEncode(url, options) {
277
  const TIMEOUT = 30000;
×
278
  if (options.cacheBust) {
×
279
    // Cache bypass so we dont have CORS issues with cached images
280
    // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
281
    url += (/\?/.test(url) ? '&' : '?') + new Date().getTime();
×
282
  }
283

284
  return new Promise(resolve => {
×
285
    const request = new Window.XMLHttpRequest();
×
286

287
    request.onreadystatechange = done;
×
288
    request.ontimeout = timeout;
×
289
    request.responseType = 'blob';
×
290
    request.timeout = TIMEOUT;
×
291
    request.open('GET', url, true);
×
292
    request.send();
×
293

294
    let placeholder;
295
    if (options.imagePlaceholder) {
×
296
      const split = options.imagePlaceholder.split(/,/);
×
297
      if (split && split[1]) {
×
298
        placeholder = split[1];
×
299
      }
300
    }
301

302
    function done() {
303
      if (request.readyState !== 4) return;
×
304

305
      if (request.status !== 200) {
×
306
        if (placeholder) {
×
307
          resolve(placeholder);
×
308
        } else {
309
          fail(`cannot fetch resource: ${url}, status: ${request.status}`);
×
310
        }
311

312
        return;
×
313
      }
314

315
      const encoder = new Window.FileReader();
×
316
      encoder.onloadend = () => {
×
317
        const content = encoder.result.split(/,/)[1];
×
318
        resolve(content);
×
319
      };
320
      encoder.readAsDataURL(request.response);
×
321
    }
322

323
    function timeout() {
324
      if (placeholder) {
×
325
        resolve(placeholder);
×
326
      } else {
327
        fail(`timeout of ${TIMEOUT}ms occurred while fetching resource: ${url}`);
×
328
      }
329
    }
330

331
    function fail(message) {
332
      Console.error(message);
×
333
      resolve('');
×
334
    }
335
  });
336
}
337

338
export function concatAndResolveUrl(base, url) {
339
  return new URL(url, base).href;
3✔
340
}
341

342
// Set relative URL in stylesheet to absolute url
343
export function setStyleSheetBaseHref(text, base) {
344
  function addBaseHrefToUrl(match, p1) {
345
    const url = /^http/i.test(p1) ? p1 : concatAndResolveUrl(base, p1);
4✔
346
    return `url('${url}')`;
4✔
347
  }
348
  return isSrcAsDataUrl(text)
5✔
349
    ? text
350
    : text.replace(/url\(['"]?([^'"]+?)['"]?\)/g, addBaseHrefToUrl);
351
}
352

353
export function toStyleSheet(text) {
354
  const doc = document.implementation.createHTMLDocument('');
×
355
  const styleElement = document.createElement('style');
×
356

357
  styleElement.textContent = text;
×
358
  doc.body.appendChild(styleElement);
×
359

360
  return styleElement.sheet;
×
361
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc