locizify 9.0.9 → 9.0.10

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.
@@ -408,6 +408,7 @@
408
408
  }
409
409
  forward(args, lvl, prefix, debugOnly) {
410
410
  if (debugOnly && !this.debug) return null;
411
+ args = args.map(a => isString(a) ? a.replace(/[\r\n\x00-\x1F\x7F]/g, ' ') : a);
411
412
  if (isString(args[0])) args[0] = "".concat(prefix).concat(this.prefix, " ").concat(args[0]);
412
413
  return this.logger[lvl](args);
413
414
  }
@@ -1300,8 +1301,8 @@
1300
1301
  this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
1301
1302
  this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
1302
1303
  this.formatSeparator = formatSeparator || ',';
1303
- this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
1304
- this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
1304
+ this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix ? regexEscape(unescapePrefix) : '-';
1305
+ this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix ? regexEscape(unescapeSuffix) : '';
1305
1306
  this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
1306
1307
  this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
1307
1308
  this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
@@ -1345,6 +1346,9 @@
1345
1346
  }));
1346
1347
  };
1347
1348
  this.resetRegExp();
1349
+ if (!this.escapeValue && typeof str === 'string' && /\$t\([^)]*\{[^}]*\{\{/.test(str)) {
1350
+ this.logger.warn('nesting options string contains interpolated variables with escapeValue: false — ' + 'if any of those values are attacker-controlled they can inject additional ' + 'nesting options (e.g. redirect lng/ns). Sanitise untrusted input before passing ' + 'it to t(), or keep escapeValue: true.');
1351
+ }
1348
1352
  var missingInterpolationHandler = (options === null || options === void 0 ? void 0 : options.missingInterpolationHandler) || this.options.missingInterpolationHandler;
1349
1353
  var skipOnVariables = (options === null || options === void 0 || (_options$interpolatio2 = options.interpolation) === null || _options$interpolatio2 === void 0 ? void 0 : _options$interpolatio2.skipOnVariables) !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
1350
1354
  var todos = [{
@@ -2021,7 +2025,7 @@
2021
2025
  deferred.resolve(t);
2022
2026
  callback(err, t);
2023
2027
  };
2024
- if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
2028
+ if ((this.languages || this.isLanguageChangingTo) && !this.isInitialized) return finish(null, this.t.bind(this));
2025
2029
  this.changeLanguage(this.options.lng, finish);
2026
2030
  };
2027
2031
  if (this.options.resources || !this.options.initAsync) {
@@ -2403,6 +2407,66 @@
2403
2407
  var loadNamespaces = instance.loadNamespaces;
2404
2408
  var loadLanguages = instance.loadLanguages;
2405
2409
 
2410
+ function _createForOfIteratorHelper(r, e) {
2411
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
2412
+ if (!t) {
2413
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
2414
+ t && (r = t);
2415
+ var _n = 0,
2416
+ F = function F() {};
2417
+ return {
2418
+ s: F,
2419
+ n: function n() {
2420
+ return _n >= r.length ? {
2421
+ done: !0
2422
+ } : {
2423
+ done: !1,
2424
+ value: r[_n++]
2425
+ };
2426
+ },
2427
+ e: function e(r) {
2428
+ throw r;
2429
+ },
2430
+ f: F
2431
+ };
2432
+ }
2433
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2434
+ }
2435
+ var o,
2436
+ a = !0,
2437
+ u = !1;
2438
+ return {
2439
+ s: function s() {
2440
+ t = t.call(r);
2441
+ },
2442
+ n: function n() {
2443
+ var r = t.next();
2444
+ return a = r.done, r;
2445
+ },
2446
+ e: function e(r) {
2447
+ u = !0, o = r;
2448
+ },
2449
+ f: function f() {
2450
+ try {
2451
+ a || null == t.return || t.return();
2452
+ } finally {
2453
+ if (u) throw o;
2454
+ }
2455
+ }
2456
+ };
2457
+ }
2458
+ function _unsupportedIterableToArray(r, a) {
2459
+ if (r) {
2460
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
2461
+ var t = {}.toString.call(r).slice(8, -1);
2462
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
2463
+ }
2464
+ }
2465
+ function _arrayLikeToArray(r, a) {
2466
+ (null == a || a > r.length) && (a = r.length);
2467
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
2468
+ return n;
2469
+ }
2406
2470
  function _typeof(o) {
2407
2471
  "@babel/helpers - typeof";
2408
2472
 
@@ -2412,6 +2476,35 @@
2412
2476
  return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
2413
2477
  }, _typeof(o);
2414
2478
  }
2479
+ var UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
2480
+ function isSafeUrlSegment(v) {
2481
+ if (typeof v !== 'string') return false;
2482
+ if (v.length === 0 || v.length > 128) return false;
2483
+ if (UNSAFE_KEYS.indexOf(v) > -1) return false;
2484
+ if (v.indexOf('..') > -1) return false;
2485
+ if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
2486
+ if (/[?#%\s@]/.test(v)) return false;
2487
+ if (/[\x00-\x1F\x7F]/.test(v)) return false;
2488
+ return true;
2489
+ }
2490
+ function sanitizeLogValue(v) {
2491
+ if (typeof v !== 'string') return v;
2492
+ return v.replace(/[\r\n\x00-\x1F\x7F]/g, ' ');
2493
+ }
2494
+ function redactUrlCredentials(u) {
2495
+ if (typeof u !== 'string' || u.length === 0) return u;
2496
+ try {
2497
+ var parsed = new URL(u);
2498
+ if (parsed.username || parsed.password) {
2499
+ parsed.username = '';
2500
+ parsed.password = '';
2501
+ return parsed.toString();
2502
+ }
2503
+ return u;
2504
+ } catch (e) {
2505
+ return u.replace(/(\/\/)[^/@\s]+@/g, '$1');
2506
+ }
2507
+ }
2415
2508
  function hasXMLHttpRequest() {
2416
2509
  return typeof XMLHttpRequest === 'function' || (typeof XMLHttpRequest === "undefined" ? "undefined" : _typeof(XMLHttpRequest)) === 'object';
2417
2510
  }
@@ -2425,11 +2518,32 @@
2425
2518
  return Promise.resolve(maybePromise);
2426
2519
  }
2427
2520
  var interpolationRegexp = /\{\{(.+?)\}\}/g;
2428
- function interpolate(str, data) {
2429
- return str.replace(interpolationRegexp, function (match, key) {
2430
- var value = data[key.trim()];
2431
- return value != null ? value : match;
2521
+ function interpolateUrl(str, data) {
2522
+ var unsafe = false;
2523
+ var result = str.replace(interpolationRegexp, function (match, key) {
2524
+ var k = key.trim();
2525
+ if (UNSAFE_KEYS.indexOf(k) > -1) return match;
2526
+ var value = data[k];
2527
+ if (value == null) return match;
2528
+ var segments = String(value).split('+');
2529
+ var _iterator = _createForOfIteratorHelper(segments),
2530
+ _step;
2531
+ try {
2532
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2533
+ var seg = _step.value;
2534
+ if (!isSafeUrlSegment(seg)) {
2535
+ unsafe = true;
2536
+ return match;
2537
+ }
2538
+ }
2539
+ } catch (err) {
2540
+ _iterator.e(err);
2541
+ } finally {
2542
+ _iterator.f();
2543
+ }
2544
+ return segments.join('+');
2432
2545
  });
2546
+ return unsafe ? null : result;
2433
2547
  }
2434
2548
 
2435
2549
  function ownKeys$1(e, r) {
@@ -2514,10 +2628,13 @@
2514
2628
  }).catch(function () {});
2515
2629
  } catch (e) {}
2516
2630
  }
2631
+ var UNSAFE_KEYS$1 = ['__proto__', 'constructor', 'prototype'];
2517
2632
  var addQueryString = function addQueryString(url, params) {
2518
2633
  if (params && _typeof$1(params) === 'object') {
2519
2634
  var queryString = '';
2520
- for (var paramName in params) {
2635
+ for (var _i = 0, _Object$keys = Object.keys(params); _i < _Object$keys.length; _i++) {
2636
+ var paramName = _Object$keys[_i];
2637
+ if (UNSAFE_KEYS$1.indexOf(paramName) > -1) continue;
2521
2638
  queryString += '&' + encodeURIComponent(paramName) + '=' + encodeURIComponent(params[paramName]);
2522
2639
  }
2523
2640
  if (!queryString) return url;
@@ -2550,7 +2667,6 @@
2550
2667
  fetchApi(url, fetchOptions).then(resolver).catch(callback);
2551
2668
  }
2552
2669
  };
2553
- var omitFetchOptions = false;
2554
2670
  var requestWithFetch = function requestWithFetch(options, url, payload, callback) {
2555
2671
  if (options.queryStringParams) {
2556
2672
  url = addQueryString(url, options.queryStringParams);
@@ -2565,7 +2681,7 @@
2565
2681
  method: payload ? 'POST' : 'GET',
2566
2682
  body: payload ? options.stringify(payload) : undefined,
2567
2683
  headers: headers
2568
- }, omitFetchOptions ? {} : reqOptions);
2684
+ }, options._omitFetchOptions ? {} : reqOptions);
2569
2685
  var altFetch = typeof options.alternateFetch === 'function' && options.alternateFetch.length >= 1 ? options.alternateFetch : undefined;
2570
2686
  try {
2571
2687
  fetchIt(url, fetchOptions, callback, altFetch);
@@ -2578,7 +2694,7 @@
2578
2694
  delete fetchOptions[opt];
2579
2695
  });
2580
2696
  fetchIt(url, fetchOptions, callback, altFetch);
2581
- omitFetchOptions = true;
2697
+ options._omitFetchOptions = true;
2582
2698
  } catch (err) {
2583
2699
  callback(err);
2584
2700
  }
@@ -2607,7 +2723,9 @@
2607
2723
  var h = options.customHeaders;
2608
2724
  h = typeof h === 'function' ? h() : h;
2609
2725
  if (h) {
2610
- for (var i in h) {
2726
+ for (var _i2 = 0, _Object$keys2 = Object.keys(h); _i2 < _Object$keys2.length; _i2++) {
2727
+ var i = _Object$keys2[_i2];
2728
+ if (UNSAFE_KEYS$1.indexOf(i) > -1) continue;
2611
2729
  x.setRequestHeader(i, h[i]);
2612
2730
  }
2613
2731
  }
@@ -2779,10 +2897,15 @@
2779
2897
  loadPath = makePromise(loadPath);
2780
2898
  loadPath.then(function (resolvedLoadPath) {
2781
2899
  if (!resolvedLoadPath) return callback(null, {});
2782
- var url = interpolate(resolvedLoadPath, {
2900
+ var url = interpolateUrl(resolvedLoadPath, {
2783
2901
  lng: languages.join('+'),
2784
2902
  ns: namespaces.join('+')
2785
2903
  });
2904
+ if (url == null) {
2905
+ var safeLngs = languages.map(sanitizeLogValue).join(', ');
2906
+ var safeNss = namespaces.map(sanitizeLogValue).join(', ');
2907
+ return callback(new Error('i18next-http-backend: unsafe lng/ns value — refusing to build request URL for languages=[' + safeLngs + '] namespaces=[' + safeNss + ']'), false);
2908
+ }
2786
2909
  _this2.loadUrl(url, callback, loadUrlLanguages, loadUrlNamespaces);
2787
2910
  });
2788
2911
  }
@@ -2793,16 +2916,17 @@
2793
2916
  var lng = typeof languages === 'string' ? [languages] : languages;
2794
2917
  var ns = typeof namespaces === 'string' ? [namespaces] : namespaces;
2795
2918
  var payload = this.options.parseLoadPayload(lng, ns);
2919
+ var safeUrl = sanitizeLogValue(redactUrlCredentials(url));
2796
2920
  this.options.request(this.options, url, payload, function (err, res) {
2797
- if (res && (res.status >= 500 && res.status < 600 || !res.status)) return callback('failed loading ' + url + '; status code: ' + res.status, true);
2798
- if (res && res.status >= 400 && res.status < 500) return callback('failed loading ' + url + '; status code: ' + res.status, false);
2921
+ if (res && (res.status >= 500 && res.status < 600 || !res.status)) return callback('failed loading ' + safeUrl + '; status code: ' + res.status, true);
2922
+ if (res && res.status >= 400 && res.status < 500) return callback('failed loading ' + safeUrl + '; status code: ' + res.status, false);
2799
2923
  if (!res && err && err.message) {
2800
2924
  var errorMessage = err.message.toLowerCase();
2801
2925
  var isNetworkError = ['failed', 'fetch', 'network', 'load'].find(function (term) {
2802
2926
  return errorMessage.indexOf(term) > -1;
2803
2927
  });
2804
2928
  if (isNetworkError) {
2805
- return callback('failed loading ' + url + ': ' + err.message, true);
2929
+ return callback('failed loading ' + safeUrl + ': ' + sanitizeLogValue(err.message), true);
2806
2930
  }
2807
2931
  }
2808
2932
  if (err) return callback(err, false);
@@ -2814,7 +2938,7 @@
2814
2938
  ret = res.data;
2815
2939
  }
2816
2940
  } catch (e) {
2817
- parseErr = 'failed parsing ' + url + ' to json';
2941
+ parseErr = 'failed parsing ' + safeUrl + ' to json';
2818
2942
  }
2819
2943
  if (parseErr) return callback(parseErr, false);
2820
2944
  callback(null, ret);
@@ -2835,10 +2959,15 @@
2835
2959
  if (typeof _this4.options.addPath === 'function') {
2836
2960
  addPath = _this4.options.addPath(lng, namespace);
2837
2961
  }
2838
- var url = interpolate(addPath, {
2962
+ var url = interpolateUrl(addPath, {
2839
2963
  lng: lng,
2840
2964
  ns: namespace
2841
2965
  });
2966
+ if (url == null) {
2967
+ finished += 1;
2968
+ if (callback && finished === languages.length) callback(dataArray, resArray);
2969
+ return;
2970
+ }
2842
2971
  _this4.options.request(_this4.options, url, payload, function (data, res) {
2843
2972
  finished += 1;
2844
2973
  dataArray.push(data);
@@ -4656,8 +4785,12 @@
4656
4785
  return value;
4657
4786
  };
4658
4787
  DOMElement.prototype.removeAttributeNS = function _Element_removeAttributeNS(namespace, name) {
4788
+ // Prevent prototype pollution by checking if namespace is a direct property
4789
+ if (!Object.prototype.hasOwnProperty.call(this._attributes, namespace)) {
4790
+ return;
4791
+ }
4659
4792
  var attributes = this._attributes[namespace];
4660
- if (attributes) {
4793
+ if (attributes && Object.prototype.hasOwnProperty.call(attributes, name)) {
4661
4794
  delete attributes[name];
4662
4795
  }
4663
4796
  };
@@ -6782,6 +6915,16 @@
6782
6915
  }
6783
6916
  var replaceInside = ['src', 'href'];
6784
6917
  var REGEXP = new RegExp('%7B%7B(.+?)%7D%7D', 'g'); // urlEncoded {{}}
6918
+
6919
+ // Reject URL schemes that execute script when used in href/src — regardless
6920
+ // of whether the attacker-controlled value reaches the attribute via a
6921
+ // compromised translation backend, a compromised translation file, or a
6922
+ // local override. The list covers the concrete known-exploitable schemes;
6923
+ // legitimate translation use cases never need them.
6924
+ var DANGEROUS_URL_SCHEMES = /^\s*(javascript|data|vbscript|file)\s*:/i;
6925
+ function isDangerousUrl(value) {
6926
+ return typeof value === 'string' && DANGEROUS_URL_SCHEMES.test(value);
6927
+ }
6785
6928
  function translateProps(node, props) {
6786
6929
  var tOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
6787
6930
  var overrideKey = arguments.length > 3 ? arguments[3] : undefined;
@@ -6831,7 +6974,13 @@
6831
6974
  mem.splice(index - 1, 1);
6832
6975
  }
6833
6976
  }
6834
- mem.push(tr);
6977
+ // Drop dangerous URL schemes (javascript:/data:/vbscript:/file:)
6978
+ // no legitimate translation needs them in src/href.
6979
+ if (isDangerousUrl(tr)) {
6980
+ mem.push('');
6981
+ } else {
6982
+ mem.push(tr);
6983
+ }
6835
6984
  }
6836
6985
  return mem;
6837
6986
  }, arr);
@@ -6910,7 +7059,20 @@
6910
7059
  }
6911
7060
 
6912
7061
  // translate that's children and surround it again with a dummy node to parse to vdom
6913
- var translation = "<i18nextifydummy>".concat(translate(key, tOptions, overrideKey), "</i18nextifydummy>");
7062
+ var translated = translate(key, tOptions, overrideKey);
7063
+ // Optional sanitize hook — if the application has configured
7064
+ // `i18next.options.sanitize` (e.g. wired to DOMPurify), run the raw
7065
+ // translation through it before parsing into the virtual DOM.
7066
+ // Default is pass-through because i18nextify's whole purpose is to
7067
+ // render HTML from translations; sanitisation is only safe for apps
7068
+ // where translation content may not be fully trusted.
7069
+ if (typeof instance.options.sanitize === 'function') {
7070
+ translated = instance.options.sanitize(translated, {
7071
+ key: key,
7072
+ attribute: null
7073
+ });
7074
+ }
7075
+ var translation = "<i18nextifydummy>".concat(translated, "</i18nextifydummy>");
6914
7076
  var newNode = vdomParser((translation || '').trim());
6915
7077
 
6916
7078
  // replace children on passed in node
@@ -7088,8 +7250,24 @@
7088
7250
  cleanWhitespace: true,
7089
7251
  nsSeparator: '#||#',
7090
7252
  keySeparator: '#|#',
7091
- debug: window.location.search && window.location.search.indexOf('debug=true') > -1,
7092
- saveMissing: window.location.search && window.location.search.indexOf('saveMissing=true') > -1,
7253
+ // Use URLSearchParams for exact parameter lookup — a previous substring
7254
+ // match (`indexOf('debug=true')`) activated these modes for any URL that
7255
+ // happened to contain the substring (`?nosaveMissing=true` enabled
7256
+ // saveMissing; `?track_debug=true` enabled debug).
7257
+ debug: function () {
7258
+ try {
7259
+ return new URLSearchParams(window.location.search).get('debug') === 'true';
7260
+ } catch (e) {
7261
+ return false;
7262
+ }
7263
+ }(),
7264
+ saveMissing: function () {
7265
+ try {
7266
+ return new URLSearchParams(window.location.search).get('saveMissing') === 'true';
7267
+ } catch (e) {
7268
+ return false;
7269
+ }
7270
+ }(),
7093
7271
  namespace: scriptEle && scriptEle.getAttribute('namespace') || false,
7094
7272
  namespaceFromPath: scriptEle && (scriptEle.getAttribute('namespacefrompath') || scriptEle.getAttribute('namespaceFromPath')) || false,
7095
7273
  missingKeyHandler: missingHandler,
@@ -7226,19 +7404,96 @@
7226
7404
  forceRerender: forceRerender
7227
7405
  };
7228
7406
 
7407
+ function _createForOfIteratorHelper$1(r, e) {
7408
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
7409
+ if (!t) {
7410
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray$1(r)) || e && r && "number" == typeof r.length) {
7411
+ t && (r = t);
7412
+ var _n = 0,
7413
+ F = function F() {};
7414
+ return {
7415
+ s: F,
7416
+ n: function n() {
7417
+ return _n >= r.length ? {
7418
+ done: !0
7419
+ } : {
7420
+ done: !1,
7421
+ value: r[_n++]
7422
+ };
7423
+ },
7424
+ e: function e(r) {
7425
+ throw r;
7426
+ },
7427
+ f: F
7428
+ };
7429
+ }
7430
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
7431
+ }
7432
+ var o,
7433
+ a = !0,
7434
+ u = !1;
7435
+ return {
7436
+ s: function s() {
7437
+ t = t.call(r);
7438
+ },
7439
+ n: function n() {
7440
+ var r = t.next();
7441
+ return a = r.done, r;
7442
+ },
7443
+ e: function e(r) {
7444
+ u = !0, o = r;
7445
+ },
7446
+ f: function f() {
7447
+ try {
7448
+ a || null == t.return || t.return();
7449
+ } finally {
7450
+ if (u) throw o;
7451
+ }
7452
+ }
7453
+ };
7454
+ }
7455
+ function _unsupportedIterableToArray$1(r, a) {
7456
+ if (r) {
7457
+ if ("string" == typeof r) return _arrayLikeToArray$1(r, a);
7458
+ var t = {}.toString.call(r).slice(8, -1);
7459
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$1(r, a) : void 0;
7460
+ }
7461
+ }
7462
+ function _arrayLikeToArray$1(r, a) {
7463
+ (null == a || a > r.length) && (a = r.length);
7464
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
7465
+ return n;
7466
+ }
7229
7467
  var arr = [];
7230
7468
  var each = arr.forEach;
7231
7469
  var slice$2 = arr.slice;
7470
+ var UNSAFE_KEYS$2 = ['__proto__', 'constructor', 'prototype'];
7232
7471
  function defaults$1(obj) {
7233
7472
  each.call(slice$2.call(arguments, 1), function (source) {
7234
7473
  if (source) {
7235
- for (var prop in source) {
7474
+ for (var _i = 0, _Object$keys = Object.keys(source); _i < _Object$keys.length; _i++) {
7475
+ var prop = _Object$keys[_i];
7476
+ if (UNSAFE_KEYS$2.indexOf(prop) > -1) continue;
7236
7477
  if (obj[prop] === undefined) obj[prop] = source[prop];
7237
7478
  }
7238
7479
  }
7239
7480
  });
7240
7481
  return obj;
7241
7482
  }
7483
+ function isSafeUrlSegment$1(v) {
7484
+ if (typeof v !== 'string') return false;
7485
+ if (v.length === 0 || v.length > 128) return false;
7486
+ if (UNSAFE_KEYS$2.indexOf(v) > -1) return false;
7487
+ if (v.indexOf('..') > -1) return false;
7488
+ if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
7489
+ if (/[?#%\s@]/.test(v)) return false;
7490
+ if (/[\x00-\x1F\x7F]/.test(v)) return false;
7491
+ return true;
7492
+ }
7493
+ function sanitizeLogValue$1(v) {
7494
+ if (typeof v !== 'string') return v;
7495
+ return v.replace(/[\r\n\x00-\x1F\x7F]/g, ' ');
7496
+ }
7242
7497
  function debounce$1(func, wait, immediate) {
7243
7498
  var timeout;
7244
7499
  return function () {
@@ -7297,20 +7552,47 @@
7297
7552
  if (object == null) return '';
7298
7553
  return '' + object;
7299
7554
  }
7300
- function interpolate$1(str, data, lng) {
7301
- var match, value;
7302
- function regexSafe(val) {
7303
- return val.replace(/\$/g, '$$$$');
7304
- }
7555
+ function interpolateUrl$1(str, data) {
7556
+ var match;
7557
+ var unsafe = false;
7305
7558
  while (match = regexp.exec(str)) {
7306
- value = match[1].trim();
7307
- if (typeof value !== 'string') value = makeString$1(value);
7308
- if (!value) value = '';
7309
- value = regexSafe(value);
7310
- str = str.replace(match[0], data[value] || value);
7559
+ var key = match[1].trim();
7560
+ if (UNSAFE_KEYS$2.indexOf(key) > -1) {
7561
+ regexp.lastIndex = 0;
7562
+ continue;
7563
+ }
7564
+ var raw = data[key];
7565
+ if (raw == null) {
7566
+ regexp.lastIndex = 0;
7567
+ continue;
7568
+ }
7569
+ var value = makeString$1(raw);
7570
+ var segments = value.split('+');
7571
+ var segmentsOk = true;
7572
+ var _iterator = _createForOfIteratorHelper$1(segments),
7573
+ _step;
7574
+ try {
7575
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
7576
+ var seg = _step.value;
7577
+ if (!isSafeUrlSegment$1(seg)) {
7578
+ segmentsOk = false;
7579
+ break;
7580
+ }
7581
+ }
7582
+ } catch (err) {
7583
+ _iterator.e(err);
7584
+ } finally {
7585
+ _iterator.f();
7586
+ }
7587
+ if (!segmentsOk) {
7588
+ unsafe = true;
7589
+ break;
7590
+ }
7591
+ str = str.replace(match[0], segments.join('+'));
7311
7592
  regexp.lastIndex = 0;
7312
7593
  }
7313
- return str;
7594
+ regexp.lastIndex = 0;
7595
+ return unsafe ? null : str;
7314
7596
  }
7315
7597
  function isMissingOption(obj, props) {
7316
7598
  return props.reduce(function (mem, p) {
@@ -7793,14 +8075,18 @@
7793
8075
  callback(new Error(isMissing));
7794
8076
  return deferred;
7795
8077
  }
7796
- var url = interpolate$1(this.options.getLanguagesPath, {
8078
+ var url = interpolateUrl$1(this.options.getLanguagesPath, {
7797
8079
  projectId: this.options.projectId
7798
8080
  });
8081
+ if (url == null) {
8082
+ callback(new Error('i18next-locize-backend: unsafe projectId — refusing to build request URL for projectId=' + sanitizeLogValue$1(String(this.options.projectId))));
8083
+ return deferred;
8084
+ }
7799
8085
  if (!this.isProjectNotExisting && this.storage.isProjectNotExisting(this.options.projectId)) {
7800
8086
  this.isProjectNotExisting = true;
7801
8087
  }
7802
8088
  if (this.isProjectNotExisting) {
7803
- callback(new Error(this.isProjectNotExistingErrorMessage || "locize project ".concat(this.options.projectId, " does not exist!")));
8089
+ callback(new Error(this.isProjectNotExistingErrorMessage || "Locize project ".concat(this.options.projectId, " does not exist!")));
7804
8090
  return deferred;
7805
8091
  }
7806
8092
  this.getLanguagesCalls = this.getLanguagesCalls || [];
@@ -7809,16 +8095,17 @@
7809
8095
  this.loadUrl({}, url, function (err, ret, info) {
7810
8096
  if (!_this3.somethingLoaded && info && info.resourceNotExisting) {
7811
8097
  _this3.isProjectNotExisting = true;
7812
- var errMsg = "locize project ".concat(_this3.options.projectId, " does not exist!");
8098
+ var errMsg = "Locize project ".concat(_this3.options.projectId, " does not exist!");
7813
8099
  _this3.isProjectNotExistingErrorMessage = errMsg;
7814
8100
  var cdnTypeAlt = _this3.options.cdnType === 'standard' ? 'pro' : 'standard';
7815
8101
  var otherEndpointApiPaths = getApiPaths(cdnTypeAlt);
7816
- var urlAlt = interpolate$1(otherEndpointApiPaths.getLanguagesPath, {
8102
+ var urlAlt = interpolateUrl$1(otherEndpointApiPaths.getLanguagesPath, {
7817
8103
  projectId: _this3.options.projectId
7818
8104
  });
8105
+ if (urlAlt == null) return;
7819
8106
  _this3.loadUrl({}, urlAlt, function (errAlt, retAlt, infoAlt) {
7820
8107
  if (!errAlt && retAlt && (!infoAlt || !infoAlt.resourceNotExisting)) {
7821
- errMsg += " It seems you're using the wrong cdnType. Your locize project is configured to use \"".concat(cdnTypeAlt, "\" but here you've configured \"").concat(_this3.options.cdnType, "\".");
8108
+ errMsg += " It seems you're using the wrong cdnType. Your Locize project is configured to use \"".concat(cdnTypeAlt, "\" but here you've configured \"").concat(_this3.options.cdnType, "\".");
7822
8109
  _this3.isProjectNotExistingErrorMessage = errMsg;
7823
8110
  } else if (!_this3.somethingLoaded && infoAlt && infoAlt.resourceNotExisting) {
7824
8111
  _this3.isProjectNotExisting = true;
@@ -7948,7 +8235,7 @@
7948
8235
  if (this.options.private) {
7949
8236
  var isMissing = isMissingOption(this.options, ['projectId', 'version', 'apiKey']);
7950
8237
  if (isMissing) return callback(new Error(isMissing), false);
7951
- url = interpolate$1(this.options.privatePath, {
8238
+ url = interpolateUrl$1(this.options.privatePath, {
7952
8239
  lng: language,
7953
8240
  ns: namespace,
7954
8241
  projectId: this.options.projectId,
@@ -7960,24 +8247,27 @@
7960
8247
  } else {
7961
8248
  var _isMissing = isMissingOption(this.options, ['projectId', 'version']);
7962
8249
  if (_isMissing) return callback(new Error(_isMissing), false);
7963
- url = interpolate$1(this.options.loadPath, {
8250
+ url = interpolateUrl$1(this.options.loadPath, {
7964
8251
  lng: language,
7965
8252
  ns: namespace,
7966
8253
  projectId: this.options.projectId,
7967
8254
  version: this.options.version
7968
8255
  });
7969
8256
  }
8257
+ if (url == null) {
8258
+ return callback(new Error('i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to build request URL for lng=' + sanitizeLogValue$1(String(language)) + ' ns=' + sanitizeLogValue$1(String(namespace))), false);
8259
+ }
7970
8260
  if (!this.isProjectNotExisting && this.storage.isProjectNotExisting(this.options.projectId)) {
7971
8261
  this.isProjectNotExisting = true;
7972
8262
  }
7973
8263
  if (this.isProjectNotExisting) {
7974
- var err = new Error(this.isProjectNotExistingErrorMessage || "locize project ".concat(this.options.projectId, " does not exist!"));
8264
+ var err = new Error(this.isProjectNotExistingErrorMessage || "Locize project ".concat(this.options.projectId, " does not exist!"));
7975
8265
  if (logger) logger.error(err.message);
7976
8266
  if (callback) callback(err);
7977
8267
  return;
7978
8268
  }
7979
8269
  if (this.warnedLanguages && this.warnedLanguages.indexOf(language) > -1) {
7980
- var _err = new Error("Will not continue to load language \"".concat(language, "\" since it is not available in locize project ").concat(this.options.projectId, "!"));
8270
+ var _err = new Error("Will not continue to load language \"".concat(language, "\" since it is not available in Locize project ").concat(this.options.projectId, "!"));
7981
8271
  if (logger) logger.error(_err.message);
7982
8272
  if (callback) callback(_err);
7983
8273
  return;
@@ -7996,7 +8286,7 @@
7996
8286
  if (_this6.warnedLanguages && _this6.warnedLanguages.indexOf(language) > -1) return;
7997
8287
  _this6.warnedLanguages || (_this6.warnedLanguages = []);
7998
8288
  _this6.warnedLanguages.push(language);
7999
- if (logger) logger.error("Language \"".concat(language, "\" is not available in locize project ").concat(_this6.options.projectId, "!"));
8289
+ if (logger) logger.error("Language \"".concat(language, "\" is not available in Locize project ").concat(_this6.options.projectId, "!"));
8000
8290
  });
8001
8291
  }, randomizeTimeout(_this6.options.checkForProjectTimeout));
8002
8292
  }
@@ -8125,18 +8415,24 @@
8125
8415
  }, {
8126
8416
  key: "writePage",
8127
8417
  value: function writePage(lng, namespace, missings, callback) {
8128
- var missingUrl = interpolate$1(this.options.addPath, {
8418
+ var missingUrl = interpolateUrl$1(this.options.addPath, {
8129
8419
  lng: lng,
8130
8420
  ns: namespace,
8131
8421
  projectId: this.options.projectId,
8132
8422
  version: this.options.version
8133
8423
  });
8134
- var updatesUrl = interpolate$1(this.options.updatePath, {
8424
+ var updatesUrl = interpolateUrl$1(this.options.updatePath, {
8135
8425
  lng: lng,
8136
8426
  ns: namespace,
8137
8427
  projectId: this.options.projectId,
8138
8428
  version: this.options.version
8139
8429
  });
8430
+ if (missingUrl == null || updatesUrl == null) {
8431
+ if (typeof callback === 'function') {
8432
+ callback(new Error('i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to persist missing keys for lng=' + sanitizeLogValue$1(String(lng)) + ' ns=' + sanitizeLogValue$1(String(namespace))));
8433
+ }
8434
+ return;
8435
+ }
8140
8436
  var hasMissing = false;
8141
8437
  var hasUpdates = false;
8142
8438
  var payloadMissing = {};
@@ -8589,17 +8885,17 @@
8589
8885
  }
8590
8886
  }
8591
8887
 
8592
- function _arrayLikeToArray(r, a) {
8888
+ function _arrayLikeToArray$2(r, a) {
8593
8889
  (null == a || a > r.length) && (a = r.length);
8594
8890
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
8595
8891
  return n;
8596
8892
  }
8597
8893
 
8598
- function _unsupportedIterableToArray(r, a) {
8894
+ function _unsupportedIterableToArray$2(r, a) {
8599
8895
  if (r) {
8600
- if ("string" == typeof r) return _arrayLikeToArray(r, a);
8896
+ if ("string" == typeof r) return _arrayLikeToArray$2(r, a);
8601
8897
  var t = {}.toString.call(r).slice(8, -1);
8602
- return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
8898
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$2(r, a) : void 0;
8603
8899
  }
8604
8900
  }
8605
8901
 
@@ -8608,7 +8904,7 @@
8608
8904
  }
8609
8905
 
8610
8906
  function _slicedToArray$1(r, e) {
8611
- return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
8907
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray$2(r, e) || _nonIterableRest();
8612
8908
  }
8613
8909
 
8614
8910
  function debounce$2(func, wait, immediate) {
@@ -8865,8 +9161,17 @@
8865
9161
  sendMessage('added', msg);
8866
9162
  }
8867
9163
  };
9164
+ var getExpectedIframeOrigin = function getExpectedIframeOrigin() {
9165
+ try {
9166
+ return new URL(getIframeUrl()).origin;
9167
+ } catch (err) {
9168
+ return null;
9169
+ }
9170
+ };
8868
9171
  if (typeof window !== 'undefined') {
8869
9172
  window.addEventListener('message', function (e) {
9173
+ var expectedOrigin = getExpectedIframeOrigin();
9174
+ if (!expectedOrigin || e.origin !== expectedOrigin) return;
8870
9175
  var _e$data = e.data,
8871
9176
  sender = _e$data.sender,
8872
9177
  action = _e$data.action,
@@ -8887,6 +9192,45 @@
8887
9192
  });
8888
9193
  }
8889
9194
 
9195
+ var DANGEROUS_ATTR_NAMES = /^(on\w+|style)$/i;
9196
+ var URL_ATTR_NAMES = /^(href|src|action|formaction|xlink:href)$/i;
9197
+ var DANGEROUS_URL_SCHEMES$1 = /^\s*(javascript|data|vbscript|file)\s*:/i;
9198
+ function isSafeAttributeWrite(attr, value) {
9199
+ if (typeof attr !== 'string') return false;
9200
+ if (DANGEROUS_ATTR_NAMES.test(attr)) return false;
9201
+ if (URL_ATTR_NAMES.test(attr) && typeof value === 'string' && DANGEROUS_URL_SCHEMES$1.test(value)) return false;
9202
+ return true;
9203
+ }
9204
+ function sanitizeTranslationHtml(html) {
9205
+ if (typeof html !== 'string') return html;
9206
+ if (typeof DOMParser === 'undefined') return html;
9207
+ try {
9208
+ var doc = new DOMParser().parseFromString("<body>".concat(html, "</body>"), 'text/html');
9209
+ var disallowedTags = ['SCRIPT', 'IFRAME', 'OBJECT', 'EMBED', 'LINK', 'META', 'BASE', 'STYLE'];
9210
+ disallowedTags.forEach(function (tag) {
9211
+ doc.body.querySelectorAll(tag.toLowerCase()).forEach(function (n) {
9212
+ return n.remove();
9213
+ });
9214
+ });
9215
+ doc.body.querySelectorAll('*').forEach(function (n) {
9216
+ var attrs = Array.from(n.attributes);
9217
+ attrs.forEach(function (a) {
9218
+ var name = a.name;
9219
+ var val = a.value;
9220
+ if (/^on/i.test(name)) {
9221
+ n.removeAttribute(name);
9222
+ return;
9223
+ }
9224
+ if (URL_ATTR_NAMES.test(name) && DANGEROUS_URL_SCHEMES$1.test(val)) {
9225
+ n.removeAttribute(name);
9226
+ }
9227
+ });
9228
+ });
9229
+ return doc.body.innerHTML;
9230
+ } catch (e) {
9231
+ return html;
9232
+ }
9233
+ }
8890
9234
  function setValueOnNode(meta, value) {
8891
9235
  var item = store.get(meta.eleUniqueID);
8892
9236
  if (!item || !item.keys[meta.textType]) return;
@@ -8895,6 +9239,7 @@
8895
9239
  item.node.textContent = txtWithHiddenMeta;
8896
9240
  } else if (meta.textType.indexOf('attr:') === 0) {
8897
9241
  var attr = meta.textType.replace('attr:', '');
9242
+ if (!isSafeAttributeWrite(attr, txtWithHiddenMeta)) return;
8898
9243
  item.node.setAttribute(attr, txtWithHiddenMeta);
8899
9244
  } else if (meta.textType === 'html') {
8900
9245
  var id = "".concat(meta.textType, "-").concat(meta.children);
@@ -8905,13 +9250,14 @@
8905
9250
  });
8906
9251
  item.originalChildNodes = clones;
8907
9252
  }
9253
+ var sanitisedHtml = sanitizeTranslationHtml(txtWithHiddenMeta);
8908
9254
  if (item.children[id].length === item.node.childNodes.length) {
8909
- item.node.innerHTML = txtWithHiddenMeta;
9255
+ item.node.innerHTML = sanitisedHtml;
8910
9256
  } else {
8911
9257
  var children = item.children[id];
8912
9258
  var first = children[0].child;
8913
9259
  var dummy = document.createElement('div');
8914
- dummy.innerHTML = txtWithHiddenMeta;
9260
+ dummy.innerHTML = sanitisedHtml;
8915
9261
  var nodes = [];
8916
9262
  dummy.childNodes.forEach(function (c) {
8917
9263
  nodes.push(c);
@@ -8954,7 +9300,7 @@
8954
9300
  api.addHandler('commitKey', handler$1);
8955
9301
 
8956
9302
  function _arrayWithoutHoles(r) {
8957
- if (Array.isArray(r)) return _arrayLikeToArray(r);
9303
+ if (Array.isArray(r)) return _arrayLikeToArray$2(r);
8958
9304
  }
8959
9305
 
8960
9306
  function _iterableToArray(r) {
@@ -8966,7 +9312,7 @@
8966
9312
  }
8967
9313
 
8968
9314
  function _toConsumableArray(r) {
8969
- return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
9315
+ return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray$2(r) || _nonIterableSpread();
8970
9316
  }
8971
9317
 
8972
9318
  function handler$2(payload) {
@@ -9067,9 +9413,23 @@
9067
9413
  return refEle;
9068
9414
  }
9069
9415
 
9416
+ function isOccluded(node) {
9417
+ var rect = node.getBoundingClientRect();
9418
+ if (!rect.width || !rect.height) return true;
9419
+ var x = rect.left + rect.width / 2;
9420
+ var y = rect.top + rect.height / 2;
9421
+ var topEl = document.elementFromPoint(x, y);
9422
+ if (!topEl) return true;
9423
+ if (topEl.dataset && topEl.dataset.i18nextEditorElement === 'true') return false;
9424
+ return !node.contains(topEl) && !topEl.contains(node);
9425
+ }
9070
9426
  var debouncedUpdateDistance = debounce$2(function (e, observer) {
9071
9427
  Object.values(store.data).forEach(function (item) {
9072
9428
  if (!isInViewport(item.node)) return;
9429
+ if (isOccluded(item.node)) {
9430
+ resetHighlight(item, item.node, item.keys);
9431
+ return;
9432
+ }
9073
9433
  var distance = mouseDistanceFromElement(e, item.node);
9074
9434
  if (distance < 5) {
9075
9435
  highlight(item, item.node, item.keys);
@@ -9080,6 +9440,10 @@
9080
9440
  });
9081
9441
  Object.values(uninstrumentedStore.data).forEach(function (item) {
9082
9442
  if (!isInViewport(item.node)) return;
9443
+ if (isOccluded(item.node)) {
9444
+ resetHighlight(item, item.node, item.keys);
9445
+ return;
9446
+ }
9083
9447
  var distance = mouseDistanceFromElement(e, item.node);
9084
9448
  if (distance < 10) {
9085
9449
  highlightUninstrumented(item, item.node, item.keys);
@@ -9195,6 +9559,10 @@
9195
9559
  return popup;
9196
9560
  }
9197
9561
 
9562
+ var CSS_LENGTH_RE = /^\d+(?:\.\d+)?(?:px|%|em|rem|vh|vw|ch|ex)$/i;
9563
+ function isSafeCssLength(v) {
9564
+ return typeof v === 'string' && CSS_LENGTH_RE.test(v);
9565
+ }
9198
9566
  function handler$4(payload) {
9199
9567
  var containerStyle = payload.containerStyle;
9200
9568
  if (containerStyle) {
@@ -9208,6 +9576,12 @@
9208
9576
  containerStyle.height = storedSize.height + 'px';
9209
9577
  containerStyle.width = storedSize.width + 'px';
9210
9578
  }
9579
+ if (containerStyle.height && !isSafeCssLength(containerStyle.height)) {
9580
+ delete containerStyle.height;
9581
+ }
9582
+ if (containerStyle.width && !isSafeCssLength(containerStyle.width)) {
9583
+ delete containerStyle.width;
9584
+ }
9211
9585
  if (containerStyle.height) {
9212
9586
  var diff = "calc(".concat(containerStyle.height, " - ").concat(popup.style.height, ")");
9213
9587
  popup.style.setProperty('top', "calc(".concat(popup.style.top, " - ").concat(diff, ")"));
@@ -9218,10 +9592,10 @@
9218
9592
  popup.style.setProperty('left', "calc(".concat(popup.style.left, " - ").concat(_diff, ")"));
9219
9593
  popup.style.setProperty('width', containerStyle.width);
9220
9594
  }
9221
- if (storedPos && storedPos.top && storedPos.top < window.innerHeight - containerStyle.height.replace('px', '')) {
9595
+ if (storedPos && storedPos.top && containerStyle.height && storedPos.top < window.innerHeight - containerStyle.height.replace('px', '')) {
9222
9596
  popup.style.setProperty('top', storedPos.top + 'px');
9223
9597
  }
9224
- if (storedPos && storedPos.left && storedPos.left < window.innerWidth - containerStyle.width.replace('px', '')) {
9598
+ if (storedPos && storedPos.left && containerStyle.width && storedPos.left < window.innerWidth - containerStyle.width.replace('px', '')) {
9225
9599
  popup.style.setProperty('left', storedPos.left + 'px');
9226
9600
  }
9227
9601
  }
@@ -9404,10 +9778,6 @@
9404
9778
  bottom: 'top',
9405
9779
  top: 'bottom'
9406
9780
  };
9407
- var oppositeAlignmentMap = {
9408
- start: 'end',
9409
- end: 'start'
9410
- };
9411
9781
  function clamp(start, value, end) {
9412
9782
  return max(start, min(value, end));
9413
9783
  }
@@ -9427,7 +9797,8 @@
9427
9797
  return axis === 'y' ? 'height' : 'width';
9428
9798
  }
9429
9799
  function getSideAxis(placement) {
9430
- return ['top', 'bottom'].includes(getSide(placement)) ? 'y' : 'x';
9800
+ var firstChar = placement[0];
9801
+ return firstChar === 't' || firstChar === 'b' ? 'y' : 'x';
9431
9802
  }
9432
9803
  function getAlignmentAxis(placement) {
9433
9804
  return getOppositeAxis(getSideAxis(placement));
@@ -9450,21 +9821,21 @@
9450
9821
  return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)];
9451
9822
  }
9452
9823
  function getOppositeAlignmentPlacement(placement) {
9453
- return placement.replace(/start|end/g, alignment => oppositeAlignmentMap[alignment]);
9824
+ return placement.includes('start') ? placement.replace('start', 'end') : placement.replace('end', 'start');
9454
9825
  }
9826
+ var lrPlacement = ['left', 'right'];
9827
+ var rlPlacement = ['right', 'left'];
9828
+ var tbPlacement = ['top', 'bottom'];
9829
+ var btPlacement = ['bottom', 'top'];
9455
9830
  function getSideList(side, isStart, rtl) {
9456
- var lr = ['left', 'right'];
9457
- var rl = ['right', 'left'];
9458
- var tb = ['top', 'bottom'];
9459
- var bt = ['bottom', 'top'];
9460
9831
  switch (side) {
9461
9832
  case 'top':
9462
9833
  case 'bottom':
9463
- if (rtl) return isStart ? rl : lr;
9464
- return isStart ? lr : rl;
9834
+ if (rtl) return isStart ? rlPlacement : lrPlacement;
9835
+ return isStart ? lrPlacement : rlPlacement;
9465
9836
  case 'left':
9466
9837
  case 'right':
9467
- return isStart ? tb : bt;
9838
+ return isStart ? tbPlacement : btPlacement;
9468
9839
  default:
9469
9840
  return [];
9470
9841
  }
@@ -9481,7 +9852,8 @@
9481
9852
  return list;
9482
9853
  }
9483
9854
  function getOppositePlacement(placement) {
9484
- return placement.replace(/left|right|bottom|top/g, side => oppositeSideMap[side]);
9855
+ var side = getSide(placement);
9856
+ return oppositeSideMap[side] + placement.slice(side.length);
9485
9857
  }
9486
9858
  function expandPaddingObject(padding) {
9487
9859
  return _objectSpread2({
@@ -9576,6 +9948,78 @@
9576
9948
  return coords;
9577
9949
  }
9578
9950
 
9951
+ /**
9952
+ * Resolves with an object of overflow side offsets that determine how much the
9953
+ * element is overflowing a given clipping boundary on each side.
9954
+ * - positive = overflowing the boundary by that number of pixels
9955
+ * - negative = how many pixels left before it will overflow
9956
+ * - 0 = lies flush with the boundary
9957
+ * @see https://floating-ui.com/docs/detectOverflow
9958
+ */
9959
+ function detectOverflow(_x, _x2) {
9960
+ return _detectOverflow.apply(this, arguments);
9961
+ } // Maximum number of resets that can occur before bailing to avoid infinite reset loops.
9962
+ function _detectOverflow() {
9963
+ _detectOverflow = _asyncToGenerator(function* (state, options) {
9964
+ var _await$platform$isEle;
9965
+ if (options === void 0) {
9966
+ options = {};
9967
+ }
9968
+ var {
9969
+ x,
9970
+ y,
9971
+ platform,
9972
+ rects,
9973
+ elements,
9974
+ strategy
9975
+ } = state;
9976
+ var {
9977
+ boundary = 'clippingAncestors',
9978
+ rootBoundary = 'viewport',
9979
+ elementContext = 'floating',
9980
+ altBoundary = false,
9981
+ padding = 0
9982
+ } = evaluate(options, state);
9983
+ var paddingObject = getPaddingObject(padding);
9984
+ var altContext = elementContext === 'floating' ? 'reference' : 'floating';
9985
+ var element = elements[altBoundary ? altContext : elementContext];
9986
+ var clippingClientRect = rectToClientRect(yield platform.getClippingRect({
9987
+ element: ((_await$platform$isEle = yield platform.isElement == null ? void 0 : platform.isElement(element)) != null ? _await$platform$isEle : true) ? element : element.contextElement || (yield platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating)),
9988
+ boundary,
9989
+ rootBoundary,
9990
+ strategy
9991
+ }));
9992
+ var rect = elementContext === 'floating' ? {
9993
+ x,
9994
+ y,
9995
+ width: rects.floating.width,
9996
+ height: rects.floating.height
9997
+ } : rects.reference;
9998
+ var offsetParent = yield platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating);
9999
+ var offsetScale = (yield platform.isElement == null ? void 0 : platform.isElement(offsetParent)) ? (yield platform.getScale == null ? void 0 : platform.getScale(offsetParent)) || {
10000
+ x: 1,
10001
+ y: 1
10002
+ } : {
10003
+ x: 1,
10004
+ y: 1
10005
+ };
10006
+ var elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? yield platform.convertOffsetParentRelativeRectToViewportRelativeRect({
10007
+ elements,
10008
+ rect,
10009
+ offsetParent,
10010
+ strategy
10011
+ }) : rect);
10012
+ return {
10013
+ top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
10014
+ bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
10015
+ left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
10016
+ right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
10017
+ };
10018
+ });
10019
+ return _detectOverflow.apply(this, arguments);
10020
+ }
10021
+ var MAX_RESET_COUNT = 50;
10022
+
9579
10023
  /**
9580
10024
  * Computes the `x` and `y` coordinates that will place the floating element
9581
10025
  * next to a given reference element.
@@ -9591,7 +10035,9 @@
9591
10035
  middleware = [],
9592
10036
  platform
9593
10037
  } = config;
9594
- var validMiddleware = middleware.filter(Boolean);
10038
+ var platformWithDetectOverflow = platform.detectOverflow ? platform : _objectSpread2(_objectSpread2({}, platform), {}, {
10039
+ detectOverflow
10040
+ });
9595
10041
  var rtl = yield platform.isRTL == null ? void 0 : platform.isRTL(floating);
9596
10042
  var rects = yield platform.getElementRects({
9597
10043
  reference,
@@ -9603,13 +10049,17 @@
9603
10049
  y
9604
10050
  } = computeCoordsFromPlacement(rects, placement, rtl);
9605
10051
  var statefulPlacement = placement;
9606
- var middlewareData = {};
9607
10052
  var resetCount = 0;
9608
- for (var i = 0; i < validMiddleware.length; i++) {
10053
+ var middlewareData = {};
10054
+ for (var i = 0; i < middleware.length; i++) {
10055
+ var currentMiddleware = middleware[i];
10056
+ if (!currentMiddleware) {
10057
+ continue;
10058
+ }
9609
10059
  var {
9610
10060
  name,
9611
10061
  fn
9612
- } = validMiddleware[i];
10062
+ } = currentMiddleware;
9613
10063
  var {
9614
10064
  x: nextX,
9615
10065
  y: nextY,
@@ -9623,7 +10073,7 @@
9623
10073
  strategy,
9624
10074
  middlewareData,
9625
10075
  rects,
9626
- platform,
10076
+ platform: platformWithDetectOverflow,
9627
10077
  elements: {
9628
10078
  reference,
9629
10079
  floating
@@ -9631,10 +10081,8 @@
9631
10081
  });
9632
10082
  x = nextX != null ? nextX : x;
9633
10083
  y = nextY != null ? nextY : y;
9634
- middlewareData = _objectSpread2(_objectSpread2({}, middlewareData), {}, {
9635
- [name]: _objectSpread2(_objectSpread2({}, middlewareData[name]), data)
9636
- });
9637
- if (reset && resetCount <= 50) {
10084
+ middlewareData[name] = _objectSpread2(_objectSpread2({}, middlewareData[name]), data);
10085
+ if (reset && resetCount < MAX_RESET_COUNT) {
9638
10086
  resetCount++;
9639
10087
  if (typeof reset === 'object') {
9640
10088
  if (reset.placement) {
@@ -9663,86 +10111,16 @@
9663
10111
  middlewareData
9664
10112
  };
9665
10113
  });
9666
- return function computePosition(_x, _x2, _x3) {
10114
+ return function computePosition(_x3, _x4, _x5) {
9667
10115
  return _ref2.apply(this, arguments);
9668
10116
  };
9669
10117
  }();
9670
10118
 
9671
- /**
9672
- * Resolves with an object of overflow side offsets that determine how much the
9673
- * element is overflowing a given clipping boundary on each side.
9674
- * - positive = overflowing the boundary by that number of pixels
9675
- * - negative = how many pixels left before it will overflow
9676
- * - 0 = lies flush with the boundary
9677
- * @see https://floating-ui.com/docs/detectOverflow
9678
- */
9679
- function detectOverflow(_x4, _x5) {
9680
- return _detectOverflow.apply(this, arguments);
9681
- }
9682
10119
  /**
9683
10120
  * Provides data to position an inner element of the floating element so that it
9684
10121
  * appears centered to the reference element.
9685
10122
  * @see https://floating-ui.com/docs/arrow
9686
10123
  */
9687
- function _detectOverflow() {
9688
- _detectOverflow = _asyncToGenerator(function* (state, options) {
9689
- var _await$platform$isEle;
9690
- if (options === void 0) {
9691
- options = {};
9692
- }
9693
- var {
9694
- x,
9695
- y,
9696
- platform,
9697
- rects,
9698
- elements,
9699
- strategy
9700
- } = state;
9701
- var {
9702
- boundary = 'clippingAncestors',
9703
- rootBoundary = 'viewport',
9704
- elementContext = 'floating',
9705
- altBoundary = false,
9706
- padding = 0
9707
- } = evaluate(options, state);
9708
- var paddingObject = getPaddingObject(padding);
9709
- var altContext = elementContext === 'floating' ? 'reference' : 'floating';
9710
- var element = elements[altBoundary ? altContext : elementContext];
9711
- var clippingClientRect = rectToClientRect(yield platform.getClippingRect({
9712
- element: ((_await$platform$isEle = yield platform.isElement == null ? void 0 : platform.isElement(element)) != null ? _await$platform$isEle : true) ? element : element.contextElement || (yield platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating)),
9713
- boundary,
9714
- rootBoundary,
9715
- strategy
9716
- }));
9717
- var rect = elementContext === 'floating' ? {
9718
- x,
9719
- y,
9720
- width: rects.floating.width,
9721
- height: rects.floating.height
9722
- } : rects.reference;
9723
- var offsetParent = yield platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating);
9724
- var offsetScale = (yield platform.isElement == null ? void 0 : platform.isElement(offsetParent)) ? (yield platform.getScale == null ? void 0 : platform.getScale(offsetParent)) || {
9725
- x: 1,
9726
- y: 1
9727
- } : {
9728
- x: 1,
9729
- y: 1
9730
- };
9731
- var elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? yield platform.convertOffsetParentRelativeRectToViewportRelativeRect({
9732
- elements,
9733
- rect,
9734
- offsetParent,
9735
- strategy
9736
- }) : rect);
9737
- return {
9738
- top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
9739
- bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
9740
- left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
9741
- right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
9742
- };
9743
- });
9744
- return _detectOverflow.apply(this, arguments);
9745
- }
9746
10124
  var arrow = options => ({
9747
10125
  name: 'arrow',
9748
10126
  options,
@@ -9873,7 +10251,7 @@
9873
10251
  fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
9874
10252
  }
9875
10253
  var placements = [initialPlacement, ...fallbackPlacements];
9876
- var overflow = yield detectOverflow(state, detectOverflowOptions);
10254
+ var overflow = yield platform.detectOverflow(state, detectOverflowOptions);
9877
10255
  var overflows = [];
9878
10256
  var overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
9879
10257
  if (checkMainAxis) {
@@ -9894,16 +10272,22 @@
9894
10272
  var nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
9895
10273
  var nextPlacement = placements[nextIndex];
9896
10274
  if (nextPlacement) {
9897
- // Try next placement and re-run the lifecycle.
9898
- return {
9899
- data: {
9900
- index: nextIndex,
9901
- overflows: overflowsData
9902
- },
9903
- reset: {
9904
- placement: nextPlacement
9905
- }
9906
- };
10275
+ var ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== getSideAxis(nextPlacement) : false;
10276
+ if (!ignoreCrossAxisOverflow ||
10277
+ // We leave the current main axis only if every placement on that axis
10278
+ // overflows the main axis.
10279
+ overflowsData.every(d => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) {
10280
+ // Try next placement and re-run the lifecycle.
10281
+ return {
10282
+ data: {
10283
+ index: nextIndex,
10284
+ overflows: overflowsData
10285
+ },
10286
+ reset: {
10287
+ placement: nextPlacement
10288
+ }
10289
+ };
10290
+ }
9907
10291
  }
9908
10292
 
9909
10293
  // First, find the candidates that fit on the mainAxis side of overflow,
@@ -9949,6 +10333,7 @@
9949
10333
  }
9950
10334
  };
9951
10335
  };
10336
+ var originSides = /*#__PURE__*/new Set(['left', 'top']);
9952
10337
 
9953
10338
  // For type backwards-compatibility, the `OffsetOptions` type was also
9954
10339
  // Derivable.
@@ -9973,7 +10358,7 @@
9973
10358
  var side = getSide(placement);
9974
10359
  var alignment = getAlignment(placement);
9975
10360
  var isVertical = getSideAxis(placement) === 'y';
9976
- var mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1;
10361
+ var mainAxisMulti = originSides.has(side) ? -1 : 1;
9977
10362
  var crossAxisMulti = rtl && isVertical ? -1 : 1;
9978
10363
  var rawValue = evaluate(options, state);
9979
10364
 
@@ -10056,7 +10441,8 @@
10056
10441
  var {
10057
10442
  x,
10058
10443
  y,
10059
- placement
10444
+ placement,
10445
+ platform
10060
10446
  } = state;
10061
10447
  var _evaluate4 = evaluate(options, state),
10062
10448
  {
@@ -10080,7 +10466,7 @@
10080
10466
  x,
10081
10467
  y
10082
10468
  };
10083
- var overflow = yield detectOverflow(state, detectOverflowOptions);
10469
+ var overflow = yield platform.detectOverflow(state, detectOverflowOptions);
10084
10470
  var crossAxis = getSideAxis(getSide(placement));
10085
10471
  var mainAxis = getOppositeAxis(crossAxis);
10086
10472
  var mainAxisCoord = coords[mainAxis];
@@ -10168,28 +10554,36 @@
10168
10554
  overflowX,
10169
10555
  overflowY,
10170
10556
  display
10171
- } = getComputedStyle(element);
10172
- return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display);
10557
+ } = getComputedStyle$1(element);
10558
+ return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && display !== 'inline' && display !== 'contents';
10173
10559
  }
10174
10560
  function isTableElement(element) {
10175
- return ['table', 'td', 'th'].includes(getNodeName(element));
10561
+ return /^(table|td|th)$/.test(getNodeName(element));
10176
10562
  }
10177
10563
  function isTopLayer(element) {
10178
- return [':popover-open', ':modal'].some(selector => {
10179
- try {
10180
- return element.matches(selector);
10181
- } catch (e) {
10182
- return false;
10564
+ try {
10565
+ if (element.matches(':popover-open')) {
10566
+ return true;
10183
10567
  }
10184
- });
10568
+ } catch (_e) {
10569
+ // no-op
10570
+ }
10571
+ try {
10572
+ return element.matches(':modal');
10573
+ } catch (_e) {
10574
+ return false;
10575
+ }
10185
10576
  }
10577
+ var willChangeRe = /transform|translate|scale|rotate|perspective|filter/;
10578
+ var containRe = /paint|layout|strict|content/;
10579
+ var isNotNone = value => !!value && value !== 'none';
10580
+ var isWebKitValue;
10186
10581
  function isContainingBlock(elementOrCss) {
10187
- var webkit = isWebKit();
10188
- var css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
10582
+ var css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss;
10189
10583
 
10190
10584
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
10191
10585
  // https://drafts.csswg.org/css-transforms-2/#individual-transforms
10192
- return ['transform', 'translate', 'scale', 'rotate', 'perspective'].some(value => css[value] ? css[value] !== 'none' : false) || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'translate', 'scale', 'rotate', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value));
10586
+ return isNotNone(css.transform) || isNotNone(css.translate) || isNotNone(css.scale) || isNotNone(css.rotate) || isNotNone(css.perspective) || !isWebKit() && (isNotNone(css.backdropFilter) || isNotNone(css.filter)) || willChangeRe.test(css.willChange || '') || containRe.test(css.contain || '');
10193
10587
  }
10194
10588
  function getContainingBlock(element) {
10195
10589
  var currentNode = getParentNode(element);
@@ -10204,13 +10598,15 @@
10204
10598
  return null;
10205
10599
  }
10206
10600
  function isWebKit() {
10207
- if (typeof CSS === 'undefined' || !CSS.supports) return false;
10208
- return CSS.supports('-webkit-backdrop-filter', 'none');
10601
+ if (isWebKitValue == null) {
10602
+ isWebKitValue = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('-webkit-backdrop-filter', 'none');
10603
+ }
10604
+ return isWebKitValue;
10209
10605
  }
10210
10606
  function isLastTraversableNode(node) {
10211
- return ['html', 'body', '#document'].includes(getNodeName(node));
10607
+ return /^(html|body|#document)$/.test(getNodeName(node));
10212
10608
  }
10213
- function getComputedStyle(element) {
10609
+ function getComputedStyle$1(element) {
10214
10610
  return getWindow(element).getComputedStyle(element);
10215
10611
  }
10216
10612
  function getNodeScroll(element) {
@@ -10264,15 +10660,16 @@
10264
10660
  if (isBody) {
10265
10661
  var frameElement = getFrameElement(win);
10266
10662
  return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []);
10663
+ } else {
10664
+ return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
10267
10665
  }
10268
- return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
10269
10666
  }
10270
10667
  function getFrameElement(win) {
10271
10668
  return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
10272
10669
  }
10273
10670
 
10274
10671
  function getCssDimensions(element) {
10275
- var css = getComputedStyle(element);
10672
+ var css = getComputedStyle$1(element);
10276
10673
  // In testing environments, the `width` and `height` properties are empty
10277
10674
  // strings for SVG elements, returning NaN. Fallback to `0` in this case.
10278
10675
  var width = parseFloat(css.width) || 0;
@@ -10373,7 +10770,7 @@
10373
10770
  while (currentIFrame && offsetParent && offsetWin !== currentWin) {
10374
10771
  var iframeScale = getScale(currentIFrame);
10375
10772
  var iframeRect = currentIFrame.getBoundingClientRect();
10376
- var css = getComputedStyle(currentIFrame);
10773
+ var css = getComputedStyle$1(currentIFrame);
10377
10774
  var left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
10378
10775
  var top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
10379
10776
  x *= iframeScale.x;
@@ -10403,14 +10800,9 @@
10403
10800
  }
10404
10801
  return rect.left + leftScroll;
10405
10802
  }
10406
- function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {
10407
- if (ignoreScrollbarX === void 0) {
10408
- ignoreScrollbarX = false;
10409
- }
10803
+ function getHTMLOffset(documentElement, scroll) {
10410
10804
  var htmlRect = documentElement.getBoundingClientRect();
10411
- var x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 :
10412
- // RTL <body> scrollbar.
10413
- getWindowScrollBarX(documentElement, htmlRect));
10805
+ var x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect);
10414
10806
  var y = htmlRect.top + scroll.scrollTop;
10415
10807
  return {
10416
10808
  x,
@@ -10441,14 +10833,14 @@
10441
10833
  if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
10442
10834
  scroll = getNodeScroll(offsetParent);
10443
10835
  }
10444
- if (isHTMLElement(offsetParent)) {
10836
+ if (isOffsetParentAnElement) {
10445
10837
  var offsetRect = getBoundingClientRect(offsetParent);
10446
10838
  scale = getScale(offsetParent);
10447
10839
  offsets.x = offsetRect.x + offsetParent.clientLeft;
10448
10840
  offsets.y = offsetRect.y + offsetParent.clientTop;
10449
10841
  }
10450
10842
  }
10451
- var htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0);
10843
+ var htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
10452
10844
  return {
10453
10845
  width: rect.width * scale.x,
10454
10846
  height: rect.height * scale.y,
@@ -10470,7 +10862,7 @@
10470
10862
  var height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
10471
10863
  var x = -scroll.scrollLeft + getWindowScrollBarX(element);
10472
10864
  var y = -scroll.scrollTop;
10473
- if (getComputedStyle(body).direction === 'rtl') {
10865
+ if (getComputedStyle$1(body).direction === 'rtl') {
10474
10866
  x += max(html.clientWidth, body.clientWidth) - width;
10475
10867
  }
10476
10868
  return {
@@ -10480,6 +10872,11 @@
10480
10872
  y
10481
10873
  };
10482
10874
  }
10875
+
10876
+ // Safety check: ensure the scrollbar space is reasonable in case this
10877
+ // calculation is affected by unusual styles.
10878
+ // Most scrollbars leave 15-18px of space.
10879
+ var SCROLLBAR_MAX = 25;
10483
10880
  function getViewportRect(element, strategy) {
10484
10881
  var win = getWindow(element);
10485
10882
  var html = getDocumentElement(element);
@@ -10497,6 +10894,24 @@
10497
10894
  y = visualViewport.offsetTop;
10498
10895
  }
10499
10896
  }
10897
+ var windowScrollbarX = getWindowScrollBarX(html);
10898
+ // <html> `overflow: hidden` + `scrollbar-gutter: stable` reduces the
10899
+ // visual width of the <html> but this is not considered in the size
10900
+ // of `html.clientWidth`.
10901
+ if (windowScrollbarX <= 0) {
10902
+ var doc = html.ownerDocument;
10903
+ var body = doc.body;
10904
+ var bodyStyles = getComputedStyle(body);
10905
+ var bodyMarginInline = doc.compatMode === 'CSS1Compat' ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0;
10906
+ var clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline);
10907
+ if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
10908
+ width -= clippingStableScrollbarWidth;
10909
+ }
10910
+ } else if (windowScrollbarX <= SCROLLBAR_MAX) {
10911
+ // If the <body> scrollbar is on the left, the width needs to be extended
10912
+ // by the scrollbar amount so there isn't extra space on the right.
10913
+ width += windowScrollbarX;
10914
+ }
10500
10915
  return {
10501
10916
  width,
10502
10917
  height,
@@ -10546,7 +10961,7 @@
10546
10961
  if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
10547
10962
  return false;
10548
10963
  }
10549
- return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
10964
+ return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
10550
10965
  }
10551
10966
 
10552
10967
  // A "clipping ancestor" is an `overflow` element with the characteristic of
@@ -10559,17 +10974,17 @@
10559
10974
  }
10560
10975
  var result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body');
10561
10976
  var currentContainingBlockComputedStyle = null;
10562
- var elementIsFixed = getComputedStyle(element).position === 'fixed';
10977
+ var elementIsFixed = getComputedStyle$1(element).position === 'fixed';
10563
10978
  var currentNode = elementIsFixed ? getParentNode(element) : element;
10564
10979
 
10565
10980
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
10566
10981
  while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
10567
- var computedStyle = getComputedStyle(currentNode);
10982
+ var computedStyle = getComputedStyle$1(currentNode);
10568
10983
  var currentNodeIsContaining = isContainingBlock(currentNode);
10569
10984
  if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
10570
10985
  currentContainingBlockComputedStyle = null;
10571
10986
  }
10572
- var shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
10987
+ var shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && (currentContainingBlockComputedStyle.position === 'absolute' || currentContainingBlockComputedStyle.position === 'fixed') || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
10573
10988
  if (shouldDropCurrentNode) {
10574
10989
  // Drop non-containing blocks.
10575
10990
  result = result.filter(ancestor => ancestor !== currentNode);
@@ -10594,20 +11009,23 @@
10594
11009
  } = _ref;
10595
11010
  var elementClippingAncestors = boundary === 'clippingAncestors' ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary);
10596
11011
  var clippingAncestors = [...elementClippingAncestors, rootBoundary];
10597
- var firstClippingAncestor = clippingAncestors[0];
10598
- var clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
10599
- var rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
10600
- accRect.top = max(rect.top, accRect.top);
10601
- accRect.right = min(rect.right, accRect.right);
10602
- accRect.bottom = min(rect.bottom, accRect.bottom);
10603
- accRect.left = max(rect.left, accRect.left);
10604
- return accRect;
10605
- }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
11012
+ var firstRect = getClientRectFromClippingAncestor(element, clippingAncestors[0], strategy);
11013
+ var top = firstRect.top;
11014
+ var right = firstRect.right;
11015
+ var bottom = firstRect.bottom;
11016
+ var left = firstRect.left;
11017
+ for (var i = 1; i < clippingAncestors.length; i++) {
11018
+ var rect = getClientRectFromClippingAncestor(element, clippingAncestors[i], strategy);
11019
+ top = max(rect.top, top);
11020
+ right = min(rect.right, right);
11021
+ bottom = min(rect.bottom, bottom);
11022
+ left = max(rect.left, left);
11023
+ }
10606
11024
  return {
10607
- width: clippingRect.right - clippingRect.left,
10608
- height: clippingRect.bottom - clippingRect.top,
10609
- x: clippingRect.left,
10610
- y: clippingRect.top
11025
+ width: right - left,
11026
+ height: bottom - top,
11027
+ x: left,
11028
+ y: top
10611
11029
  };
10612
11030
  }
10613
11031
  function getDimensions(element) {
@@ -10630,6 +11048,12 @@
10630
11048
  scrollTop: 0
10631
11049
  };
10632
11050
  var offsets = createCoords(0);
11051
+
11052
+ // If the <body> scrollbar appears on the left (e.g. RTL systems). Use
11053
+ // Firefox with layout.scrollbar.side = 3 in about:config to test this.
11054
+ function setLeftRTLScrollbarOffset() {
11055
+ offsets.x = getWindowScrollBarX(documentElement);
11056
+ }
10633
11057
  if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
10634
11058
  if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
10635
11059
  scroll = getNodeScroll(offsetParent);
@@ -10639,11 +11063,12 @@
10639
11063
  offsets.x = offsetRect.x + offsetParent.clientLeft;
10640
11064
  offsets.y = offsetRect.y + offsetParent.clientTop;
10641
11065
  } else if (documentElement) {
10642
- // If the <body> scrollbar appears on the left (e.g. RTL systems). Use
10643
- // Firefox with layout.scrollbar.side = 3 in about:config to test this.
10644
- offsets.x = getWindowScrollBarX(documentElement);
11066
+ setLeftRTLScrollbarOffset();
10645
11067
  }
10646
11068
  }
11069
+ if (isFixed && !isOffsetParentAnElement && documentElement) {
11070
+ setLeftRTLScrollbarOffset();
11071
+ }
10647
11072
  var htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
10648
11073
  var x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x;
10649
11074
  var y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y;
@@ -10655,10 +11080,10 @@
10655
11080
  };
10656
11081
  }
10657
11082
  function isStaticPositioned(element) {
10658
- return getComputedStyle(element).position === 'static';
11083
+ return getComputedStyle$1(element).position === 'static';
10659
11084
  }
10660
11085
  function getTrueOffsetParent(element, polyfill) {
10661
- if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
11086
+ if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') {
10662
11087
  return null;
10663
11088
  }
10664
11089
  if (polyfill) {
@@ -10722,7 +11147,7 @@
10722
11147
  };
10723
11148
  }();
10724
11149
  function isRTL(element) {
10725
- return getComputedStyle(element).direction === 'rtl';
11150
+ return getComputedStyle$1(element).direction === 'rtl';
10726
11151
  }
10727
11152
  var platform = {
10728
11153
  convertOffsetParentRelativeRectToViewportRelativeRect,
@@ -11092,11 +11517,12 @@
11092
11517
  if (child.nodeName !== '#text') return;
11093
11518
  var txt = child.textContent;
11094
11519
  if (containsOnlySpaces(txt)) return;
11095
- var hasHiddenMeta = containsHiddenMeta(txt);
11096
- var hasHiddenStartMarker = containsHiddenStartMarker(txt);
11520
+ var trimmedTxt = txt.trim();
11521
+ var hasHiddenMeta = containsHiddenMeta(trimmedTxt);
11522
+ var hasHiddenStartMarker = containsHiddenStartMarker(trimmedTxt);
11097
11523
  if (hasHiddenMeta) usedSubliminalForText = true;
11098
11524
  if (hasHiddenStartMarker && hasHiddenMeta) {
11099
- var meta = unwrap(txt);
11525
+ var meta = unwrap(trimmedTxt);
11100
11526
  uninstrumentedStore.remove(node.uniqueID, node);
11101
11527
  store.save(node.uniqueID, meta.invisibleMeta, 'text', extractHiddenMeta(node.uniqueID, 'text', meta), node);
11102
11528
  } else if (hasHiddenStartMarker) {
@@ -11219,6 +11645,8 @@
11219
11645
  if (mutation.type === 'attributes' && !validAttributes.includes(mutation.attributeName)) {
11220
11646
  return;
11221
11647
  }
11648
+ var target = mutation.target.nodeType === 3 ? mutation.target.parentElement : mutation.target;
11649
+ if (!target) return;
11222
11650
  Object.keys(mutationTriggeringElements).forEach(function (k) {
11223
11651
  var info = mutationTriggeringElements[k];
11224
11652
  if (info.lastTriggerDate + 60000 < Date.now()) {
@@ -11227,7 +11655,7 @@
11227
11655
  });
11228
11656
  if (mutation.type === 'childList') {
11229
11657
  var notOurs = 0;
11230
- if (!ignoreMutation(mutation.target)) {
11658
+ if (!ignoreMutation(target)) {
11231
11659
  mutation.addedNodes.forEach(function (n) {
11232
11660
  if (ignoreMutation(n)) return;
11233
11661
  notOurs = notOurs + 1;
@@ -11240,25 +11668,25 @@
11240
11668
  if (notOurs === 0) return;
11241
11669
  }
11242
11670
  triggerMutation = true;
11243
- if (mutation.target && mutation.target.uniqueID) {
11244
- var info = mutationTriggeringElements[mutation.target.uniqueID] || {
11671
+ if (target.uniqueID) {
11672
+ var info = mutationTriggeringElements[target.uniqueID] || {
11245
11673
  triggered: 0
11246
11674
  };
11247
11675
  info.triggered = info.triggered + 1;
11248
11676
  info.lastTriggerDate = Date.now();
11249
- mutationTriggeringElements[mutation.target.uniqueID] = info;
11677
+ mutationTriggeringElements[target.uniqueID] = info;
11250
11678
  }
11251
11679
  var includedAlready = targetEles.reduce(function (mem, element) {
11252
- if (mem || element.contains(mutation.target) || !mutation.target.parentElement) {
11680
+ if (mem || element.contains(target) || !target.parentElement) {
11253
11681
  return true;
11254
11682
  }
11255
11683
  return false;
11256
11684
  }, false);
11257
11685
  if (!includedAlready) {
11258
11686
  targetEles = targetEles.filter(function (element) {
11259
- return !mutation.target.contains(element);
11687
+ return !target.contains(element);
11260
11688
  });
11261
- targetEles.push(mutation.target);
11689
+ targetEles.push(target);
11262
11690
  }
11263
11691
  });
11264
11692
  if (triggerMutation) debouncedHandler();
@@ -11676,6 +12104,30 @@
11676
12104
  if (!results[2]) return '';
11677
12105
  return decodeURIComponent(results[2].replace(/\+/g, ' '));
11678
12106
  }
12107
+
12108
+ // Reading credentials from the URL query string is convenient for local
12109
+ // development (run `?apikey=...&projectId=...` against a demo page) but is
12110
+ // dangerous on deployed sites: an attacker-crafted link would cause the
12111
+ // victim's page to send translation data (saveMissing) to the attacker's
12112
+ // locize project, or to run against an attacker-chosen backend.
12113
+ // The feature is preserved, but a warning is emitted when the URL
12114
+ // overrides credential values on hosts that don't look like a local dev
12115
+ // environment, so maintainers can notice and decide whether to disable it.
12116
+ function isLocalDevHost() {
12117
+ if (typeof window === 'undefined' || !window.location) return false;
12118
+ var h = window.location.hostname;
12119
+ if (!h) return false;
12120
+ return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0.0.0.0' || h.endsWith('.localhost') || h.endsWith('.local');
12121
+ }
12122
+ var credentialWarningShown = false;
12123
+ function warnIfCredentialFromUrlOnProdHost(attr) {
12124
+ if (isLocalDevHost()) return;
12125
+ if (credentialWarningShown) return; // only once per page
12126
+ credentialWarningShown = true;
12127
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
12128
+ console.warn('locizify: reading credential "' + attr + '" from URL query string on a non-local host. ' + 'An attacker-crafted link can replace your locize credentials, redirecting saveMissing writes ' + 'to an attacker-chosen project. Prefer configuring credentials via the ' + '<script id="locizify" apikey="..." projectid="..."> attributes instead.');
12129
+ }
12130
+ }
11679
12131
  var originalInit = i18next$1.init;
11680
12132
  i18next$1.init = function () {
11681
12133
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -11709,11 +12161,20 @@
11709
12161
  if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
11710
12162
  if (value !== undefined && value !== null) backend[attr] = value;
11711
12163
  if (!value) {
11712
- value = getQsParameterByName$1(attr.toLowerCase());
12164
+ var lc = attr.toLowerCase();
12165
+ value = getQsParameterByName$1(lc);
11713
12166
  if (value === 'true') value = true;
11714
12167
  if (value === 'false') value = false;
11715
- if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
11716
- if (value !== undefined && value !== null) backend[attr] = value;
12168
+ if (lc === 'autopilot' && value === '') value = true;
12169
+ if (value !== undefined && value !== null) {
12170
+ backend[attr] = value;
12171
+ // Credential-bearing attributes via URL on a non-local host is
12172
+ // risky — attacker-crafted links can replace your credentials.
12173
+ // Warn once per page load so maintainers notice.
12174
+ if (lc === 'apikey' || lc === 'projectid') {
12175
+ warnIfCredentialFromUrlOnProdHost(lc);
12176
+ }
12177
+ }
11717
12178
  }
11718
12179
  });
11719
12180
  if (backend.allowedAddOrUpdateHost) {
@@ -11729,8 +12190,14 @@
11729
12190
  // call orginal callback
11730
12191
  callback(err, t);
11731
12192
  }
12193
+
12194
+ // Accept `?apikey=` from the URL query string as a fallback. On non-local
12195
+ // hosts this is risky (attacker-crafted links can replace your credentials
12196
+ // and redirect saveMissing writes to an attacker-chosen project), so warn
12197
+ // once when it happens.
11732
12198
  if (!options.backend.apiKey && getQsParameterByName$1('apikey')) {
11733
12199
  options.backend.apiKey = getQsParameterByName$1('apikey');
12200
+ warnIfCredentialFromUrlOnProdHost('apikey');
11734
12201
  }
11735
12202
  if (!options.backend.autoPilot || options.backend.autoPilot === 'false') {
11736
12203
  return originalInit.call(i18next$1, _objectSpread2(_objectSpread2({}, options), enforce), handleI18nextInitialized);