locizify 9.0.9 → 9.0.11

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,11 +9592,18 @@
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', '')) {
9222
- popup.style.setProperty('top', storedPos.top + 'px');
9595
+ var MIN_VISIBLE = 40;
9596
+ if (storedPos && typeof storedPos.top === 'number' && containerStyle.height) {
9597
+ var maxTop = Math.max(0, window.innerHeight - MIN_VISIBLE);
9598
+ var top = Math.max(0, Math.min(storedPos.top, maxTop));
9599
+ popup.style.setProperty('top', top + 'px');
9223
9600
  }
9224
- if (storedPos && storedPos.left && storedPos.left < window.innerWidth - containerStyle.width.replace('px', '')) {
9225
- popup.style.setProperty('left', storedPos.left + 'px');
9601
+ if (storedPos && typeof storedPos.left === 'number' && containerStyle.width) {
9602
+ var width = parseInt(containerStyle.width, 10) || 0;
9603
+ var minLeft = Math.min(0, MIN_VISIBLE - width);
9604
+ var maxLeft = Math.max(0, window.innerWidth - MIN_VISIBLE);
9605
+ var left = Math.max(minLeft, Math.min(storedPos.left, maxLeft));
9606
+ popup.style.setProperty('left', left + 'px');
9226
9607
  }
9227
9608
  }
9228
9609
  }
@@ -9404,10 +9785,6 @@
9404
9785
  bottom: 'top',
9405
9786
  top: 'bottom'
9406
9787
  };
9407
- var oppositeAlignmentMap = {
9408
- start: 'end',
9409
- end: 'start'
9410
- };
9411
9788
  function clamp(start, value, end) {
9412
9789
  return max(start, min(value, end));
9413
9790
  }
@@ -9427,7 +9804,8 @@
9427
9804
  return axis === 'y' ? 'height' : 'width';
9428
9805
  }
9429
9806
  function getSideAxis(placement) {
9430
- return ['top', 'bottom'].includes(getSide(placement)) ? 'y' : 'x';
9807
+ var firstChar = placement[0];
9808
+ return firstChar === 't' || firstChar === 'b' ? 'y' : 'x';
9431
9809
  }
9432
9810
  function getAlignmentAxis(placement) {
9433
9811
  return getOppositeAxis(getSideAxis(placement));
@@ -9450,21 +9828,21 @@
9450
9828
  return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)];
9451
9829
  }
9452
9830
  function getOppositeAlignmentPlacement(placement) {
9453
- return placement.replace(/start|end/g, alignment => oppositeAlignmentMap[alignment]);
9831
+ return placement.includes('start') ? placement.replace('start', 'end') : placement.replace('end', 'start');
9454
9832
  }
9833
+ var lrPlacement = ['left', 'right'];
9834
+ var rlPlacement = ['right', 'left'];
9835
+ var tbPlacement = ['top', 'bottom'];
9836
+ var btPlacement = ['bottom', 'top'];
9455
9837
  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
9838
  switch (side) {
9461
9839
  case 'top':
9462
9840
  case 'bottom':
9463
- if (rtl) return isStart ? rl : lr;
9464
- return isStart ? lr : rl;
9841
+ if (rtl) return isStart ? rlPlacement : lrPlacement;
9842
+ return isStart ? lrPlacement : rlPlacement;
9465
9843
  case 'left':
9466
9844
  case 'right':
9467
- return isStart ? tb : bt;
9845
+ return isStart ? tbPlacement : btPlacement;
9468
9846
  default:
9469
9847
  return [];
9470
9848
  }
@@ -9481,7 +9859,8 @@
9481
9859
  return list;
9482
9860
  }
9483
9861
  function getOppositePlacement(placement) {
9484
- return placement.replace(/left|right|bottom|top/g, side => oppositeSideMap[side]);
9862
+ var side = getSide(placement);
9863
+ return oppositeSideMap[side] + placement.slice(side.length);
9485
9864
  }
9486
9865
  function expandPaddingObject(padding) {
9487
9866
  return _objectSpread2({
@@ -9576,6 +9955,78 @@
9576
9955
  return coords;
9577
9956
  }
9578
9957
 
9958
+ /**
9959
+ * Resolves with an object of overflow side offsets that determine how much the
9960
+ * element is overflowing a given clipping boundary on each side.
9961
+ * - positive = overflowing the boundary by that number of pixels
9962
+ * - negative = how many pixels left before it will overflow
9963
+ * - 0 = lies flush with the boundary
9964
+ * @see https://floating-ui.com/docs/detectOverflow
9965
+ */
9966
+ function detectOverflow(_x, _x2) {
9967
+ return _detectOverflow.apply(this, arguments);
9968
+ } // Maximum number of resets that can occur before bailing to avoid infinite reset loops.
9969
+ function _detectOverflow() {
9970
+ _detectOverflow = _asyncToGenerator(function* (state, options) {
9971
+ var _await$platform$isEle;
9972
+ if (options === void 0) {
9973
+ options = {};
9974
+ }
9975
+ var {
9976
+ x,
9977
+ y,
9978
+ platform,
9979
+ rects,
9980
+ elements,
9981
+ strategy
9982
+ } = state;
9983
+ var {
9984
+ boundary = 'clippingAncestors',
9985
+ rootBoundary = 'viewport',
9986
+ elementContext = 'floating',
9987
+ altBoundary = false,
9988
+ padding = 0
9989
+ } = evaluate(options, state);
9990
+ var paddingObject = getPaddingObject(padding);
9991
+ var altContext = elementContext === 'floating' ? 'reference' : 'floating';
9992
+ var element = elements[altBoundary ? altContext : elementContext];
9993
+ var clippingClientRect = rectToClientRect(yield platform.getClippingRect({
9994
+ 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)),
9995
+ boundary,
9996
+ rootBoundary,
9997
+ strategy
9998
+ }));
9999
+ var rect = elementContext === 'floating' ? {
10000
+ x,
10001
+ y,
10002
+ width: rects.floating.width,
10003
+ height: rects.floating.height
10004
+ } : rects.reference;
10005
+ var offsetParent = yield platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating);
10006
+ var offsetScale = (yield platform.isElement == null ? void 0 : platform.isElement(offsetParent)) ? (yield platform.getScale == null ? void 0 : platform.getScale(offsetParent)) || {
10007
+ x: 1,
10008
+ y: 1
10009
+ } : {
10010
+ x: 1,
10011
+ y: 1
10012
+ };
10013
+ var elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? yield platform.convertOffsetParentRelativeRectToViewportRelativeRect({
10014
+ elements,
10015
+ rect,
10016
+ offsetParent,
10017
+ strategy
10018
+ }) : rect);
10019
+ return {
10020
+ top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
10021
+ bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
10022
+ left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
10023
+ right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
10024
+ };
10025
+ });
10026
+ return _detectOverflow.apply(this, arguments);
10027
+ }
10028
+ var MAX_RESET_COUNT = 50;
10029
+
9579
10030
  /**
9580
10031
  * Computes the `x` and `y` coordinates that will place the floating element
9581
10032
  * next to a given reference element.
@@ -9591,7 +10042,9 @@
9591
10042
  middleware = [],
9592
10043
  platform
9593
10044
  } = config;
9594
- var validMiddleware = middleware.filter(Boolean);
10045
+ var platformWithDetectOverflow = platform.detectOverflow ? platform : _objectSpread2(_objectSpread2({}, platform), {}, {
10046
+ detectOverflow
10047
+ });
9595
10048
  var rtl = yield platform.isRTL == null ? void 0 : platform.isRTL(floating);
9596
10049
  var rects = yield platform.getElementRects({
9597
10050
  reference,
@@ -9603,13 +10056,17 @@
9603
10056
  y
9604
10057
  } = computeCoordsFromPlacement(rects, placement, rtl);
9605
10058
  var statefulPlacement = placement;
9606
- var middlewareData = {};
9607
10059
  var resetCount = 0;
9608
- for (var i = 0; i < validMiddleware.length; i++) {
10060
+ var middlewareData = {};
10061
+ for (var i = 0; i < middleware.length; i++) {
10062
+ var currentMiddleware = middleware[i];
10063
+ if (!currentMiddleware) {
10064
+ continue;
10065
+ }
9609
10066
  var {
9610
10067
  name,
9611
10068
  fn
9612
- } = validMiddleware[i];
10069
+ } = currentMiddleware;
9613
10070
  var {
9614
10071
  x: nextX,
9615
10072
  y: nextY,
@@ -9623,7 +10080,7 @@
9623
10080
  strategy,
9624
10081
  middlewareData,
9625
10082
  rects,
9626
- platform,
10083
+ platform: platformWithDetectOverflow,
9627
10084
  elements: {
9628
10085
  reference,
9629
10086
  floating
@@ -9631,10 +10088,8 @@
9631
10088
  });
9632
10089
  x = nextX != null ? nextX : x;
9633
10090
  y = nextY != null ? nextY : y;
9634
- middlewareData = _objectSpread2(_objectSpread2({}, middlewareData), {}, {
9635
- [name]: _objectSpread2(_objectSpread2({}, middlewareData[name]), data)
9636
- });
9637
- if (reset && resetCount <= 50) {
10091
+ middlewareData[name] = _objectSpread2(_objectSpread2({}, middlewareData[name]), data);
10092
+ if (reset && resetCount < MAX_RESET_COUNT) {
9638
10093
  resetCount++;
9639
10094
  if (typeof reset === 'object') {
9640
10095
  if (reset.placement) {
@@ -9663,86 +10118,16 @@
9663
10118
  middlewareData
9664
10119
  };
9665
10120
  });
9666
- return function computePosition(_x, _x2, _x3) {
10121
+ return function computePosition(_x3, _x4, _x5) {
9667
10122
  return _ref2.apply(this, arguments);
9668
10123
  };
9669
10124
  }();
9670
10125
 
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
10126
  /**
9683
10127
  * Provides data to position an inner element of the floating element so that it
9684
10128
  * appears centered to the reference element.
9685
10129
  * @see https://floating-ui.com/docs/arrow
9686
10130
  */
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
10131
  var arrow = options => ({
9747
10132
  name: 'arrow',
9748
10133
  options,
@@ -9873,7 +10258,7 @@
9873
10258
  fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
9874
10259
  }
9875
10260
  var placements = [initialPlacement, ...fallbackPlacements];
9876
- var overflow = yield detectOverflow(state, detectOverflowOptions);
10261
+ var overflow = yield platform.detectOverflow(state, detectOverflowOptions);
9877
10262
  var overflows = [];
9878
10263
  var overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
9879
10264
  if (checkMainAxis) {
@@ -9894,16 +10279,22 @@
9894
10279
  var nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
9895
10280
  var nextPlacement = placements[nextIndex];
9896
10281
  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
- };
10282
+ var ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== getSideAxis(nextPlacement) : false;
10283
+ if (!ignoreCrossAxisOverflow ||
10284
+ // We leave the current main axis only if every placement on that axis
10285
+ // overflows the main axis.
10286
+ overflowsData.every(d => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) {
10287
+ // Try next placement and re-run the lifecycle.
10288
+ return {
10289
+ data: {
10290
+ index: nextIndex,
10291
+ overflows: overflowsData
10292
+ },
10293
+ reset: {
10294
+ placement: nextPlacement
10295
+ }
10296
+ };
10297
+ }
9907
10298
  }
9908
10299
 
9909
10300
  // First, find the candidates that fit on the mainAxis side of overflow,
@@ -9949,6 +10340,7 @@
9949
10340
  }
9950
10341
  };
9951
10342
  };
10343
+ var originSides = /*#__PURE__*/new Set(['left', 'top']);
9952
10344
 
9953
10345
  // For type backwards-compatibility, the `OffsetOptions` type was also
9954
10346
  // Derivable.
@@ -9973,7 +10365,7 @@
9973
10365
  var side = getSide(placement);
9974
10366
  var alignment = getAlignment(placement);
9975
10367
  var isVertical = getSideAxis(placement) === 'y';
9976
- var mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1;
10368
+ var mainAxisMulti = originSides.has(side) ? -1 : 1;
9977
10369
  var crossAxisMulti = rtl && isVertical ? -1 : 1;
9978
10370
  var rawValue = evaluate(options, state);
9979
10371
 
@@ -10056,7 +10448,8 @@
10056
10448
  var {
10057
10449
  x,
10058
10450
  y,
10059
- placement
10451
+ placement,
10452
+ platform
10060
10453
  } = state;
10061
10454
  var _evaluate4 = evaluate(options, state),
10062
10455
  {
@@ -10080,7 +10473,7 @@
10080
10473
  x,
10081
10474
  y
10082
10475
  };
10083
- var overflow = yield detectOverflow(state, detectOverflowOptions);
10476
+ var overflow = yield platform.detectOverflow(state, detectOverflowOptions);
10084
10477
  var crossAxis = getSideAxis(getSide(placement));
10085
10478
  var mainAxis = getOppositeAxis(crossAxis);
10086
10479
  var mainAxisCoord = coords[mainAxis];
@@ -10168,28 +10561,36 @@
10168
10561
  overflowX,
10169
10562
  overflowY,
10170
10563
  display
10171
- } = getComputedStyle(element);
10172
- return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display);
10564
+ } = getComputedStyle$1(element);
10565
+ return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && display !== 'inline' && display !== 'contents';
10173
10566
  }
10174
10567
  function isTableElement(element) {
10175
- return ['table', 'td', 'th'].includes(getNodeName(element));
10568
+ return /^(table|td|th)$/.test(getNodeName(element));
10176
10569
  }
10177
10570
  function isTopLayer(element) {
10178
- return [':popover-open', ':modal'].some(selector => {
10179
- try {
10180
- return element.matches(selector);
10181
- } catch (e) {
10182
- return false;
10571
+ try {
10572
+ if (element.matches(':popover-open')) {
10573
+ return true;
10183
10574
  }
10184
- });
10575
+ } catch (_e) {
10576
+ // no-op
10577
+ }
10578
+ try {
10579
+ return element.matches(':modal');
10580
+ } catch (_e) {
10581
+ return false;
10582
+ }
10185
10583
  }
10584
+ var willChangeRe = /transform|translate|scale|rotate|perspective|filter/;
10585
+ var containRe = /paint|layout|strict|content/;
10586
+ var isNotNone = value => !!value && value !== 'none';
10587
+ var isWebKitValue;
10186
10588
  function isContainingBlock(elementOrCss) {
10187
- var webkit = isWebKit();
10188
- var css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
10589
+ var css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss;
10189
10590
 
10190
10591
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
10191
10592
  // 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));
10593
+ 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
10594
  }
10194
10595
  function getContainingBlock(element) {
10195
10596
  var currentNode = getParentNode(element);
@@ -10204,13 +10605,15 @@
10204
10605
  return null;
10205
10606
  }
10206
10607
  function isWebKit() {
10207
- if (typeof CSS === 'undefined' || !CSS.supports) return false;
10208
- return CSS.supports('-webkit-backdrop-filter', 'none');
10608
+ if (isWebKitValue == null) {
10609
+ isWebKitValue = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('-webkit-backdrop-filter', 'none');
10610
+ }
10611
+ return isWebKitValue;
10209
10612
  }
10210
10613
  function isLastTraversableNode(node) {
10211
- return ['html', 'body', '#document'].includes(getNodeName(node));
10614
+ return /^(html|body|#document)$/.test(getNodeName(node));
10212
10615
  }
10213
- function getComputedStyle(element) {
10616
+ function getComputedStyle$1(element) {
10214
10617
  return getWindow(element).getComputedStyle(element);
10215
10618
  }
10216
10619
  function getNodeScroll(element) {
@@ -10264,15 +10667,16 @@
10264
10667
  if (isBody) {
10265
10668
  var frameElement = getFrameElement(win);
10266
10669
  return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []);
10670
+ } else {
10671
+ return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
10267
10672
  }
10268
- return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
10269
10673
  }
10270
10674
  function getFrameElement(win) {
10271
10675
  return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
10272
10676
  }
10273
10677
 
10274
10678
  function getCssDimensions(element) {
10275
- var css = getComputedStyle(element);
10679
+ var css = getComputedStyle$1(element);
10276
10680
  // In testing environments, the `width` and `height` properties are empty
10277
10681
  // strings for SVG elements, returning NaN. Fallback to `0` in this case.
10278
10682
  var width = parseFloat(css.width) || 0;
@@ -10373,7 +10777,7 @@
10373
10777
  while (currentIFrame && offsetParent && offsetWin !== currentWin) {
10374
10778
  var iframeScale = getScale(currentIFrame);
10375
10779
  var iframeRect = currentIFrame.getBoundingClientRect();
10376
- var css = getComputedStyle(currentIFrame);
10780
+ var css = getComputedStyle$1(currentIFrame);
10377
10781
  var left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
10378
10782
  var top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
10379
10783
  x *= iframeScale.x;
@@ -10403,14 +10807,9 @@
10403
10807
  }
10404
10808
  return rect.left + leftScroll;
10405
10809
  }
10406
- function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {
10407
- if (ignoreScrollbarX === void 0) {
10408
- ignoreScrollbarX = false;
10409
- }
10810
+ function getHTMLOffset(documentElement, scroll) {
10410
10811
  var htmlRect = documentElement.getBoundingClientRect();
10411
- var x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 :
10412
- // RTL <body> scrollbar.
10413
- getWindowScrollBarX(documentElement, htmlRect));
10812
+ var x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect);
10414
10813
  var y = htmlRect.top + scroll.scrollTop;
10415
10814
  return {
10416
10815
  x,
@@ -10441,14 +10840,14 @@
10441
10840
  if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
10442
10841
  scroll = getNodeScroll(offsetParent);
10443
10842
  }
10444
- if (isHTMLElement(offsetParent)) {
10843
+ if (isOffsetParentAnElement) {
10445
10844
  var offsetRect = getBoundingClientRect(offsetParent);
10446
10845
  scale = getScale(offsetParent);
10447
10846
  offsets.x = offsetRect.x + offsetParent.clientLeft;
10448
10847
  offsets.y = offsetRect.y + offsetParent.clientTop;
10449
10848
  }
10450
10849
  }
10451
- var htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0);
10850
+ var htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
10452
10851
  return {
10453
10852
  width: rect.width * scale.x,
10454
10853
  height: rect.height * scale.y,
@@ -10470,7 +10869,7 @@
10470
10869
  var height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
10471
10870
  var x = -scroll.scrollLeft + getWindowScrollBarX(element);
10472
10871
  var y = -scroll.scrollTop;
10473
- if (getComputedStyle(body).direction === 'rtl') {
10872
+ if (getComputedStyle$1(body).direction === 'rtl') {
10474
10873
  x += max(html.clientWidth, body.clientWidth) - width;
10475
10874
  }
10476
10875
  return {
@@ -10480,6 +10879,11 @@
10480
10879
  y
10481
10880
  };
10482
10881
  }
10882
+
10883
+ // Safety check: ensure the scrollbar space is reasonable in case this
10884
+ // calculation is affected by unusual styles.
10885
+ // Most scrollbars leave 15-18px of space.
10886
+ var SCROLLBAR_MAX = 25;
10483
10887
  function getViewportRect(element, strategy) {
10484
10888
  var win = getWindow(element);
10485
10889
  var html = getDocumentElement(element);
@@ -10497,6 +10901,24 @@
10497
10901
  y = visualViewport.offsetTop;
10498
10902
  }
10499
10903
  }
10904
+ var windowScrollbarX = getWindowScrollBarX(html);
10905
+ // <html> `overflow: hidden` + `scrollbar-gutter: stable` reduces the
10906
+ // visual width of the <html> but this is not considered in the size
10907
+ // of `html.clientWidth`.
10908
+ if (windowScrollbarX <= 0) {
10909
+ var doc = html.ownerDocument;
10910
+ var body = doc.body;
10911
+ var bodyStyles = getComputedStyle(body);
10912
+ var bodyMarginInline = doc.compatMode === 'CSS1Compat' ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0;
10913
+ var clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline);
10914
+ if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
10915
+ width -= clippingStableScrollbarWidth;
10916
+ }
10917
+ } else if (windowScrollbarX <= SCROLLBAR_MAX) {
10918
+ // If the <body> scrollbar is on the left, the width needs to be extended
10919
+ // by the scrollbar amount so there isn't extra space on the right.
10920
+ width += windowScrollbarX;
10921
+ }
10500
10922
  return {
10501
10923
  width,
10502
10924
  height,
@@ -10546,7 +10968,7 @@
10546
10968
  if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
10547
10969
  return false;
10548
10970
  }
10549
- return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
10971
+ return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
10550
10972
  }
10551
10973
 
10552
10974
  // A "clipping ancestor" is an `overflow` element with the characteristic of
@@ -10559,17 +10981,17 @@
10559
10981
  }
10560
10982
  var result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body');
10561
10983
  var currentContainingBlockComputedStyle = null;
10562
- var elementIsFixed = getComputedStyle(element).position === 'fixed';
10984
+ var elementIsFixed = getComputedStyle$1(element).position === 'fixed';
10563
10985
  var currentNode = elementIsFixed ? getParentNode(element) : element;
10564
10986
 
10565
10987
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
10566
10988
  while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
10567
- var computedStyle = getComputedStyle(currentNode);
10989
+ var computedStyle = getComputedStyle$1(currentNode);
10568
10990
  var currentNodeIsContaining = isContainingBlock(currentNode);
10569
10991
  if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
10570
10992
  currentContainingBlockComputedStyle = null;
10571
10993
  }
10572
- var shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
10994
+ var shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && (currentContainingBlockComputedStyle.position === 'absolute' || currentContainingBlockComputedStyle.position === 'fixed') || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
10573
10995
  if (shouldDropCurrentNode) {
10574
10996
  // Drop non-containing blocks.
10575
10997
  result = result.filter(ancestor => ancestor !== currentNode);
@@ -10594,20 +11016,23 @@
10594
11016
  } = _ref;
10595
11017
  var elementClippingAncestors = boundary === 'clippingAncestors' ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary);
10596
11018
  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));
11019
+ var firstRect = getClientRectFromClippingAncestor(element, clippingAncestors[0], strategy);
11020
+ var top = firstRect.top;
11021
+ var right = firstRect.right;
11022
+ var bottom = firstRect.bottom;
11023
+ var left = firstRect.left;
11024
+ for (var i = 1; i < clippingAncestors.length; i++) {
11025
+ var rect = getClientRectFromClippingAncestor(element, clippingAncestors[i], strategy);
11026
+ top = max(rect.top, top);
11027
+ right = min(rect.right, right);
11028
+ bottom = min(rect.bottom, bottom);
11029
+ left = max(rect.left, left);
11030
+ }
10606
11031
  return {
10607
- width: clippingRect.right - clippingRect.left,
10608
- height: clippingRect.bottom - clippingRect.top,
10609
- x: clippingRect.left,
10610
- y: clippingRect.top
11032
+ width: right - left,
11033
+ height: bottom - top,
11034
+ x: left,
11035
+ y: top
10611
11036
  };
10612
11037
  }
10613
11038
  function getDimensions(element) {
@@ -10630,6 +11055,12 @@
10630
11055
  scrollTop: 0
10631
11056
  };
10632
11057
  var offsets = createCoords(0);
11058
+
11059
+ // If the <body> scrollbar appears on the left (e.g. RTL systems). Use
11060
+ // Firefox with layout.scrollbar.side = 3 in about:config to test this.
11061
+ function setLeftRTLScrollbarOffset() {
11062
+ offsets.x = getWindowScrollBarX(documentElement);
11063
+ }
10633
11064
  if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
10634
11065
  if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
10635
11066
  scroll = getNodeScroll(offsetParent);
@@ -10639,11 +11070,12 @@
10639
11070
  offsets.x = offsetRect.x + offsetParent.clientLeft;
10640
11071
  offsets.y = offsetRect.y + offsetParent.clientTop;
10641
11072
  } 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);
11073
+ setLeftRTLScrollbarOffset();
10645
11074
  }
10646
11075
  }
11076
+ if (isFixed && !isOffsetParentAnElement && documentElement) {
11077
+ setLeftRTLScrollbarOffset();
11078
+ }
10647
11079
  var htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
10648
11080
  var x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x;
10649
11081
  var y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y;
@@ -10655,10 +11087,10 @@
10655
11087
  };
10656
11088
  }
10657
11089
  function isStaticPositioned(element) {
10658
- return getComputedStyle(element).position === 'static';
11090
+ return getComputedStyle$1(element).position === 'static';
10659
11091
  }
10660
11092
  function getTrueOffsetParent(element, polyfill) {
10661
- if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
11093
+ if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') {
10662
11094
  return null;
10663
11095
  }
10664
11096
  if (polyfill) {
@@ -10722,7 +11154,7 @@
10722
11154
  };
10723
11155
  }();
10724
11156
  function isRTL(element) {
10725
- return getComputedStyle(element).direction === 'rtl';
11157
+ return getComputedStyle$1(element).direction === 'rtl';
10726
11158
  }
10727
11159
  var platform = {
10728
11160
  convertOffsetParentRelativeRectToViewportRelativeRect,
@@ -11092,11 +11524,12 @@
11092
11524
  if (child.nodeName !== '#text') return;
11093
11525
  var txt = child.textContent;
11094
11526
  if (containsOnlySpaces(txt)) return;
11095
- var hasHiddenMeta = containsHiddenMeta(txt);
11096
- var hasHiddenStartMarker = containsHiddenStartMarker(txt);
11527
+ var trimmedTxt = txt.trim();
11528
+ var hasHiddenMeta = containsHiddenMeta(trimmedTxt);
11529
+ var hasHiddenStartMarker = containsHiddenStartMarker(trimmedTxt);
11097
11530
  if (hasHiddenMeta) usedSubliminalForText = true;
11098
11531
  if (hasHiddenStartMarker && hasHiddenMeta) {
11099
- var meta = unwrap(txt);
11532
+ var meta = unwrap(trimmedTxt);
11100
11533
  uninstrumentedStore.remove(node.uniqueID, node);
11101
11534
  store.save(node.uniqueID, meta.invisibleMeta, 'text', extractHiddenMeta(node.uniqueID, 'text', meta), node);
11102
11535
  } else if (hasHiddenStartMarker) {
@@ -11219,6 +11652,8 @@
11219
11652
  if (mutation.type === 'attributes' && !validAttributes.includes(mutation.attributeName)) {
11220
11653
  return;
11221
11654
  }
11655
+ var target = mutation.target.nodeType === 3 ? mutation.target.parentElement : mutation.target;
11656
+ if (!target) return;
11222
11657
  Object.keys(mutationTriggeringElements).forEach(function (k) {
11223
11658
  var info = mutationTriggeringElements[k];
11224
11659
  if (info.lastTriggerDate + 60000 < Date.now()) {
@@ -11227,7 +11662,7 @@
11227
11662
  });
11228
11663
  if (mutation.type === 'childList') {
11229
11664
  var notOurs = 0;
11230
- if (!ignoreMutation(mutation.target)) {
11665
+ if (!ignoreMutation(target)) {
11231
11666
  mutation.addedNodes.forEach(function (n) {
11232
11667
  if (ignoreMutation(n)) return;
11233
11668
  notOurs = notOurs + 1;
@@ -11240,25 +11675,25 @@
11240
11675
  if (notOurs === 0) return;
11241
11676
  }
11242
11677
  triggerMutation = true;
11243
- if (mutation.target && mutation.target.uniqueID) {
11244
- var info = mutationTriggeringElements[mutation.target.uniqueID] || {
11678
+ if (target.uniqueID) {
11679
+ var info = mutationTriggeringElements[target.uniqueID] || {
11245
11680
  triggered: 0
11246
11681
  };
11247
11682
  info.triggered = info.triggered + 1;
11248
11683
  info.lastTriggerDate = Date.now();
11249
- mutationTriggeringElements[mutation.target.uniqueID] = info;
11684
+ mutationTriggeringElements[target.uniqueID] = info;
11250
11685
  }
11251
11686
  var includedAlready = targetEles.reduce(function (mem, element) {
11252
- if (mem || element.contains(mutation.target) || !mutation.target.parentElement) {
11687
+ if (mem || element.contains(target) || !target.parentElement) {
11253
11688
  return true;
11254
11689
  }
11255
11690
  return false;
11256
11691
  }, false);
11257
11692
  if (!includedAlready) {
11258
11693
  targetEles = targetEles.filter(function (element) {
11259
- return !mutation.target.contains(element);
11694
+ return !target.contains(element);
11260
11695
  });
11261
- targetEles.push(mutation.target);
11696
+ targetEles.push(target);
11262
11697
  }
11263
11698
  });
11264
11699
  if (triggerMutation) debouncedHandler();
@@ -11280,6 +11715,17 @@
11280
11715
  };
11281
11716
  }
11282
11717
 
11718
+ var MIN_VISIBLE = 40;
11719
+ function clampTop(top) {
11720
+ var max = Math.max(0, window.innerHeight - MIN_VISIBLE);
11721
+ return Math.max(0, Math.min(top, max));
11722
+ }
11723
+ function clampLeft(left, el) {
11724
+ var width = el.offsetWidth || 0;
11725
+ var min = Math.min(0, MIN_VISIBLE - width);
11726
+ var max = Math.max(0, window.innerWidth - MIN_VISIBLE);
11727
+ return Math.max(min, Math.min(left, max));
11728
+ }
11283
11729
  function initDragElement() {
11284
11730
  var pos1 = 0;
11285
11731
  var pos2 = 0;
@@ -11323,8 +11769,8 @@
11323
11769
  pos2 = pos4 - e.clientY;
11324
11770
  pos3 = e.clientX;
11325
11771
  pos4 = e.clientY;
11326
- elmnt.style.top = elmnt.offsetTop - pos2 + 'px';
11327
- elmnt.style.left = elmnt.offsetLeft - pos1 + 'px';
11772
+ elmnt.style.top = clampTop(elmnt.offsetTop - pos2) + 'px';
11773
+ elmnt.style.left = clampLeft(elmnt.offsetLeft - pos1, elmnt) + 'px';
11328
11774
  }
11329
11775
  function closeDragElement() {
11330
11776
  startMouseTracking();
@@ -11676,6 +12122,30 @@
11676
12122
  if (!results[2]) return '';
11677
12123
  return decodeURIComponent(results[2].replace(/\+/g, ' '));
11678
12124
  }
12125
+
12126
+ // Reading credentials from the URL query string is convenient for local
12127
+ // development (run `?apikey=...&projectId=...` against a demo page) but is
12128
+ // dangerous on deployed sites: an attacker-crafted link would cause the
12129
+ // victim's page to send translation data (saveMissing) to the attacker's
12130
+ // locize project, or to run against an attacker-chosen backend.
12131
+ // The feature is preserved, but a warning is emitted when the URL
12132
+ // overrides credential values on hosts that don't look like a local dev
12133
+ // environment, so maintainers can notice and decide whether to disable it.
12134
+ function isLocalDevHost() {
12135
+ if (typeof window === 'undefined' || !window.location) return false;
12136
+ var h = window.location.hostname;
12137
+ if (!h) return false;
12138
+ return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0.0.0.0' || h.endsWith('.localhost') || h.endsWith('.local');
12139
+ }
12140
+ var credentialWarningShown = false;
12141
+ function warnIfCredentialFromUrlOnProdHost(attr) {
12142
+ if (isLocalDevHost()) return;
12143
+ if (credentialWarningShown) return; // only once per page
12144
+ credentialWarningShown = true;
12145
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
12146
+ 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.');
12147
+ }
12148
+ }
11679
12149
  var originalInit = i18next$1.init;
11680
12150
  i18next$1.init = function () {
11681
12151
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -11709,11 +12179,20 @@
11709
12179
  if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
11710
12180
  if (value !== undefined && value !== null) backend[attr] = value;
11711
12181
  if (!value) {
11712
- value = getQsParameterByName$1(attr.toLowerCase());
12182
+ var lc = attr.toLowerCase();
12183
+ value = getQsParameterByName$1(lc);
11713
12184
  if (value === 'true') value = true;
11714
12185
  if (value === 'false') value = false;
11715
- if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
11716
- if (value !== undefined && value !== null) backend[attr] = value;
12186
+ if (lc === 'autopilot' && value === '') value = true;
12187
+ if (value !== undefined && value !== null) {
12188
+ backend[attr] = value;
12189
+ // Credential-bearing attributes via URL on a non-local host is
12190
+ // risky — attacker-crafted links can replace your credentials.
12191
+ // Warn once per page load so maintainers notice.
12192
+ if (lc === 'apikey' || lc === 'projectid') {
12193
+ warnIfCredentialFromUrlOnProdHost(lc);
12194
+ }
12195
+ }
11717
12196
  }
11718
12197
  });
11719
12198
  if (backend.allowedAddOrUpdateHost) {
@@ -11729,8 +12208,14 @@
11729
12208
  // call orginal callback
11730
12209
  callback(err, t);
11731
12210
  }
12211
+
12212
+ // Accept `?apikey=` from the URL query string as a fallback. On non-local
12213
+ // hosts this is risky (attacker-crafted links can replace your credentials
12214
+ // and redirect saveMissing writes to an attacker-chosen project), so warn
12215
+ // once when it happens.
11732
12216
  if (!options.backend.apiKey && getQsParameterByName$1('apikey')) {
11733
12217
  options.backend.apiKey = getQsParameterByName$1('apikey');
12218
+ warnIfCredentialFromUrlOnProdHost('apikey');
11734
12219
  }
11735
12220
  if (!options.backend.autoPilot || options.backend.autoPilot === 'false') {
11736
12221
  return originalInit.call(i18next$1, _objectSpread2(_objectSpread2({}, options), enforce), handleI18nextInitialized);