@configuratorware/configurator-frontendgui 1.57.9 → 1.57.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.
@@ -39,6 +39,10 @@ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object
39
39
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
40
40
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
41
41
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
42
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
43
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
44
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
45
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
42
46
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
43
47
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); 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; } }
44
48
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
@@ -246,15 +250,32 @@ var applyVariantAmountPreSelection = exports.applyVariantAmountPreSelection = fu
246
250
  var children = (0, _flatten["default"])((0, _Selectors.getChildren)(configurator).map(function (variant) {
247
251
  return Object.values((0, _Selectors.getDeepestChildIdentifiers)(variant));
248
252
  }));
249
- for (var preSelectionVariant in itemAmounts) {
250
- if (!itemAmounts.hasOwnProperty(preSelectionVariant)) {
251
- continue;
252
- }
253
- var amount = +itemAmounts[preSelectionVariant];
254
- if (children.indexOf(preSelectionVariant) !== -1 && amount > 0) {
255
- _ServiceLocator.Services.configurator.setSelectedAmount(preSelectionVariant, amount);
256
- }
253
+ var applicableAmounts = Object.keys(itemAmounts).filter(function (identifier) {
254
+ return children.indexOf(identifier) !== -1 && +itemAmounts[identifier] > 0;
255
+ }).map(function (identifier) {
256
+ return [identifier, +itemAmounts[identifier]];
257
+ });
258
+
259
+ // designerPreSelection is parsed once from the URL and stays in conf for the whole session, so
260
+ // without consuming it here it would re-apply (and re-clear existing amounts) on every later
261
+ // configuration load, e.g. switching the configuration variant or loading a saved configuration
262
+ (0, _configuration.setConf)('designerPreSelection.itemAmounts', undefined);
263
+ if (applicableAmounts.length === 0) {
264
+ return;
257
265
  }
266
+
267
+ // only the explicitly passed amounts should apply, so clear whatever the item already had - by
268
+ // the keys actually selected, since a selected amount can be stored at any item level, not just
269
+ // on the deepest children
270
+ Object.keys((0, _Selectors.getSelectedAmounts)(configurator) || {}).forEach(function (identifier) {
271
+ return _ServiceLocator.Services.configurator.setSelectedAmount(identifier, 0);
272
+ });
273
+ applicableAmounts.forEach(function (_ref4) {
274
+ var _ref5 = _slicedToArray(_ref4, 2),
275
+ identifier = _ref5[0],
276
+ amount = _ref5[1];
277
+ return _ServiceLocator.Services.configurator.setSelectedAmount(identifier, amount);
278
+ });
258
279
  }
259
280
  };
260
281
  var selectOptionclassification = exports.selectOptionclassification = function selectOptionclassification(optionclassification) {
@@ -413,7 +434,7 @@ var receiveCodesnippets = exports.receiveCodesnippets = function receiveCodesnip
413
434
  };
414
435
  var fetchConfigurationForDesignerProductPreview = exports.fetchConfigurationForDesignerProductPreview = function fetchConfigurationForDesignerProductPreview() {
415
436
  return /*#__PURE__*/function () {
416
- var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(dispatch, getState) {
437
+ var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(dispatch, getState) {
417
438
  var itemIdentifier, _getAvailableConfigur2, designerEnabled, initialDesignData;
418
439
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
419
440
  while (1) switch (_context3.prev = _context3.next) {
@@ -448,7 +469,7 @@ var fetchConfigurationForDesignerProductPreview = exports.fetchConfigurationForD
448
469
  }, _callee3);
449
470
  }));
450
471
  return function (_x4, _x5) {
451
- return _ref4.apply(this, arguments);
472
+ return _ref6.apply(this, arguments);
452
473
  };
453
474
  }();
454
475
  };
@@ -866,10 +887,10 @@ var getSwitchOptionData = function getSwitchOptionData(optionsList) {
866
887
  try {
867
888
  for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
868
889
  var component = _step4.value;
869
- switchOptionData[component.identifier] = component.selectedoptions.map(function (_ref5) {
870
- var identifier = _ref5.identifier,
871
- amount = _ref5.amount,
872
- inputText = _ref5.inputText;
890
+ switchOptionData[component.identifier] = component.selectedoptions.map(function (_ref7) {
891
+ var identifier = _ref7.identifier,
892
+ amount = _ref7.amount,
893
+ inputText = _ref7.inputText;
873
894
  return {
874
895
  identifier: identifier,
875
896
  inputText: inputText,
@@ -902,7 +923,7 @@ var viewOptionDetails = exports.viewOptionDetails = function viewOptionDetails(o
902
923
  };
903
924
  var loadConfiguration = exports.loadConfiguration = function loadConfiguration() {
904
925
  return /*#__PURE__*/function () {
905
- var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(dispatch, getState) {
926
+ var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(dispatch, getState) {
906
927
  var loadCode, action;
907
928
  return _regeneratorRuntime().wrap(function _callee4$(_context4) {
908
929
  while (1) switch (_context4.prev = _context4.next) {
@@ -939,7 +960,7 @@ var loadConfiguration = exports.loadConfiguration = function loadConfiguration()
939
960
  }, _callee4, null, [[5, 13, 17, 20]]);
940
961
  }));
941
962
  return function (_x6, _x7) {
942
- return _ref6.apply(this, arguments);
963
+ return _ref8.apply(this, arguments);
943
964
  };
944
965
  }();
945
966
  };
@@ -952,7 +973,7 @@ var receiveDesignerData = exports.receiveDesignerData = function receiveDesigner
952
973
  };
953
974
  var loadDesignerData = exports.loadDesignerData = function loadDesignerData(configuration) {
954
975
  return /*#__PURE__*/function () {
955
- var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(dispatch) {
976
+ var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(dispatch) {
956
977
  return _regeneratorRuntime().wrap(function _callee5$(_context5) {
957
978
  while (1) switch (_context5.prev = _context5.next) {
958
979
  case 0:
@@ -986,7 +1007,7 @@ var loadDesignerData = exports.loadDesignerData = function loadDesignerData(conf
986
1007
  }, _callee5, null, [[4, 10, 13, 16]]);
987
1008
  }));
988
1009
  return function (_x8) {
989
- return _ref7.apply(this, arguments);
1010
+ return _ref9.apply(this, arguments);
990
1011
  };
991
1012
  }();
992
1013
  };
@@ -1907,6 +1907,11 @@ var DesignDataService = exports["default"] = /*#__PURE__*/function () {
1907
1907
  state,
1908
1908
  showHighlight,
1909
1909
  staticBorderColor,
1910
+ dynamicDesignAreaHighlightBorderColor,
1911
+ dynamicDesignAreaHighlightBorderColorMode,
1912
+ getDesignAreaUnderlyingColor,
1913
+ allDesignAreasForBorderColor,
1914
+ borderColor,
1910
1915
  textureBuilderConfig,
1911
1916
  variantIdentifier,
1912
1917
  effectEnabled,
@@ -1919,11 +1924,16 @@ var DesignDataService = exports["default"] = /*#__PURE__*/function () {
1919
1924
  state = _ServiceLocator.Services.store.state;
1920
1925
  showHighlight = (0, _get2["default"])(state, 'designArea.showHighlight');
1921
1926
  staticBorderColor = (0, _Selectors3.getClientHighlightColor)(state);
1927
+ dynamicDesignAreaHighlightBorderColor = (0, _configuration.getConf)('visualization.dynamicDesignAreaHighlightBorderColor');
1928
+ dynamicDesignAreaHighlightBorderColorMode = (0, _configuration.getConf)('visualization.dynamicDesignAreaHighlightBorderColorMode');
1929
+ getDesignAreaUnderlyingColor = function getDesignAreaUnderlyingColor(identifier) {
1930
+ return _ServiceLocator.Services.visualization.getDesignAreaUnderlyingColor(identifier);
1931
+ }; // 2D invokes drawingFn once per area with a single-element list; unify borders from the full view.
1932
+ allDesignAreasForBorderColor = this.getDesignAreas();
1933
+ borderColor = dynamicDesignAreaHighlightBorderColor ? (0, _getDesignAreaBorderColor.createBorderColorResolver)(getDesignAreaUnderlyingColor, staticBorderColor, dynamicDesignAreaHighlightBorderColorMode, allDesignAreasForBorderColor) : staticBorderColor;
1922
1934
  textureBuilderConfig = (0, _merge["default"])({
1923
1935
  highlight: {
1924
- borderColor: (0, _configuration.getConf)('visualization.dynamicDesignAreaHighlightBorderColor') ? (0, _getDesignAreaBorderColor.createBorderColorResolver)(function (identifier) {
1925
- return _ServiceLocator.Services.visualization.getDesignAreaUnderlyingColor(identifier);
1926
- }, staticBorderColor, (0, _configuration.getConf)('visualization.dynamicDesignAreaHighlightBorderColorMode')) : staticBorderColor,
1936
+ borderColor: borderColor,
1927
1937
  backgroundColor: (0, _configuration.getConf)('visualization.designAreaHighlightBackgroundColor'),
1928
1938
  lineWidth: (0, _configuration.getConf)('visualization.designAreaHighlightBorderWidth', 2)
1929
1939
  }
@@ -2119,22 +2129,22 @@ var DesignDataService = exports["default"] = /*#__PURE__*/function () {
2119
2129
  return _ref17.apply(this, arguments);
2120
2130
  };
2121
2131
  }();
2122
- _context22.prev = 8;
2123
- _context22.next = 11;
2124
- return getDrawingContext(drawingFn, true /* update alpha */, true /* clean up texture before drawing */);
2125
- case 11:
2132
+ _context22.prev = 13;
2126
2133
  _context22.next = 16;
2134
+ return getDrawingContext(drawingFn, true /* update alpha */, true /* clean up texture before drawing */);
2135
+ case 16:
2136
+ _context22.next = 21;
2127
2137
  break;
2128
- case 13:
2129
- _context22.prev = 13;
2130
- _context22.t0 = _context22["catch"](8);
2138
+ case 18:
2139
+ _context22.prev = 18;
2140
+ _context22.t0 = _context22["catch"](13);
2131
2141
  // eslint-disable-next-line no-console
2132
2142
  console.warn('Could not get drawing context, texture update is skipped!', _context22.t0.message);
2133
- case 16:
2143
+ case 21:
2134
2144
  case "end":
2135
2145
  return _context22.stop();
2136
2146
  }
2137
- }, _callee21, null, [[8, 13]]);
2147
+ }, _callee21, this, [[13, 18]]);
2138
2148
  }));
2139
2149
  function redrawAllDesignAreas(_x20, _x21) {
2140
2150
  return _redrawAllDesignAreas.apply(this, arguments);
@@ -3,9 +3,16 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.rgbToHsl = exports.invertColor = exports.getContrastBorderColor = exports.getComplementaryBorderColor = exports.getBorderColorForAverage = exports.createBorderColorResolver = exports.BorderColorModes = void 0;
6
+ exports.rgbToHsl = exports.invertColor = exports.getContrastBorderColor = exports.getComplementaryBorderColor = exports.getBorderColorForAverage = exports.createBorderColorResolver = exports.computeUnifiedContrastBorders = exports.BorderColorModes = void 0;
7
7
  var _styles = require("@material-ui/core/styles");
8
8
  var _Selectors = require("../Reducers/DesignArea/Selectors");
9
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
10
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
11
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
12
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); 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; } }
13
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
14
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
15
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
9
16
  /**
10
17
  * Minimum contrast between the border and the area it is drawn on, the WCAG AA requirement for
11
18
  * graphical objects. Below it the derived color is not considered visible enough.
@@ -112,6 +119,135 @@ var getContrastBorderColor = exports.getContrastBorderColor = function getContra
112
119
  return (0, _styles.getContrastRatio)(averageHex, NEUTRAL_DARK) >= (0, _styles.getContrastRatio)(averageHex, NEUTRAL_LIGHT) ? NEUTRAL_DARK : NEUTRAL_LIGHT;
113
120
  };
114
121
 
122
+ /**
123
+ * @param {string} neutral
124
+ * @param {Array<{averageHex: string}>} areas
125
+ * @return {number}
126
+ */
127
+ var minContrastForNeutralAcrossAreas = function minContrastForNeutralAcrossAreas(neutral, areas) {
128
+ return Math.min.apply(Math, _toConsumableArray(areas.map(function (_ref3) {
129
+ var averageHex = _ref3.averageHex;
130
+ return (0, _styles.getContrastRatio)(averageHex, neutral);
131
+ })));
132
+ };
133
+
134
+ /**
135
+ * @param {string} neutral
136
+ * @param {Array<{averageHex: string}>} areas
137
+ * @return {boolean}
138
+ */
139
+ var neutralMeetsMinContrastOnAllAreas = function neutralMeetsMinContrastOnAllAreas(neutral, areas) {
140
+ return minContrastForNeutralAcrossAreas(neutral, areas) >= MIN_CONTRAST_RATIO;
141
+ };
142
+
143
+ /**
144
+ * Picks one near-black or near-white border for every design area in a highlight pass when possible,
145
+ * so similar product shades do not flip border color at the luminance tie-break.
146
+ * @param {function(string): ({r: number, g: number, b: number}|null|undefined)} getUnderlyingColor
147
+ * @param {Array<{identifier: string}>} designAreas
148
+ * @return {Map<string, string>}
149
+ */
150
+ var computeUnifiedContrastBorders = exports.computeUnifiedContrastBorders = function computeUnifiedContrastBorders(getUnderlyingColor, designAreas) {
151
+ var areas = [];
152
+ var _iterator = _createForOfIteratorHelper(designAreas),
153
+ _step;
154
+ try {
155
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
156
+ var designArea = _step.value;
157
+ var _identifier3 = designArea && designArea.identifier;
158
+ if (!_identifier3) {
159
+ continue;
160
+ }
161
+ var average = getUnderlyingColor(_identifier3);
162
+ if (!average) {
163
+ continue;
164
+ }
165
+ areas.push({
166
+ identifier: _identifier3,
167
+ averageHex: (0, _Selectors.rgbToHex)(average.r, average.g, average.b)
168
+ });
169
+ }
170
+ } catch (err) {
171
+ _iterator.e(err);
172
+ } finally {
173
+ _iterator.f();
174
+ }
175
+ var borderByIdentifier = new Map();
176
+ if (!areas.length) {
177
+ return borderByIdentifier;
178
+ }
179
+ var darkVotes = 0;
180
+ var lightVotes = 0;
181
+ for (var _i = 0, _areas = areas; _i < _areas.length; _i++) {
182
+ var averageHex = _areas[_i].averageHex;
183
+ var vote = getContrastBorderColor(averageHex);
184
+ if (vote === NEUTRAL_DARK) {
185
+ darkVotes += 1;
186
+ } else {
187
+ lightVotes += 1;
188
+ }
189
+ }
190
+ var majorityNeutral;
191
+ if (darkVotes > lightVotes) {
192
+ majorityNeutral = NEUTRAL_DARK;
193
+ } else if (lightVotes > darkVotes) {
194
+ majorityNeutral = NEUTRAL_LIGHT;
195
+ }
196
+ if (majorityNeutral && neutralMeetsMinContrastOnAllAreas(majorityNeutral, areas)) {
197
+ var _iterator2 = _createForOfIteratorHelper(areas),
198
+ _step2;
199
+ try {
200
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
201
+ var identifier = _step2.value.identifier;
202
+ borderByIdentifier.set(identifier, majorityNeutral);
203
+ }
204
+ } catch (err) {
205
+ _iterator2.e(err);
206
+ } finally {
207
+ _iterator2.f();
208
+ }
209
+ return borderByIdentifier;
210
+ }
211
+ var qualifyingNeutrals = [NEUTRAL_DARK, NEUTRAL_LIGHT].filter(function (neutral) {
212
+ return neutralMeetsMinContrastOnAllAreas(neutral, areas);
213
+ });
214
+ if (qualifyingNeutrals.length) {
215
+ var maximinNeutral = qualifyingNeutrals.reduce(function (best, neutral) {
216
+ var minContrast = minContrastForNeutralAcrossAreas(neutral, areas);
217
+ if (!best) {
218
+ return {
219
+ neutral: neutral,
220
+ minContrast: minContrast
221
+ };
222
+ }
223
+ return minContrast > best.minContrast ? {
224
+ neutral: neutral,
225
+ minContrast: minContrast
226
+ } : best;
227
+ }, null).neutral;
228
+ var _iterator3 = _createForOfIteratorHelper(areas),
229
+ _step3;
230
+ try {
231
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
232
+ var _identifier = _step3.value.identifier;
233
+ borderByIdentifier.set(_identifier, maximinNeutral);
234
+ }
235
+ } catch (err) {
236
+ _iterator3.e(err);
237
+ } finally {
238
+ _iterator3.f();
239
+ }
240
+ return borderByIdentifier;
241
+ }
242
+ for (var _i2 = 0, _areas2 = areas; _i2 < _areas2.length; _i2++) {
243
+ var _areas2$_i = _areas2[_i2],
244
+ _identifier2 = _areas2$_i.identifier,
245
+ _averageHex = _areas2$_i.averageHex;
246
+ borderByIdentifier.set(_identifier2, getContrastBorderColor(_averageHex));
247
+ }
248
+ return borderByIdentifier;
249
+ };
250
+
115
251
  /**
116
252
  * Keeps the hue of the inverted (complementary) color at a high saturation and moves the lightness
117
253
  * away from the average, only as far as needed to reach a visible contrast, so the border is a vivid
@@ -128,8 +264,8 @@ var getComplementaryBorderColor = exports.getComplementaryBorderColor = function
128
264
  saturation = _rgbToHsl.saturation;
129
265
  if (saturation >= MIN_SATURATION) {
130
266
  var lightnessCandidates = (0, _styles.getLuminance)(averageHex) > LIGHT_AVERAGE_LUMINANCE ? LIGHTNESS_ON_LIGHT_AVERAGE : LIGHTNESS_ON_DARK_AVERAGE;
131
- for (var _i = 0, _lightnessCandidates = lightnessCandidates; _i < _lightnessCandidates.length; _i++) {
132
- var lightness = _lightnessCandidates[_i];
267
+ for (var _i3 = 0, _lightnessCandidates = lightnessCandidates; _i3 < _lightnessCandidates.length; _i3++) {
268
+ var lightness = _lightnessCandidates[_i3];
133
269
  var candidate = (0, _Selectors.rgbString2hex)((0, _styles.hslToRgb)("hsl(".concat(Math.round(hue), ", ").concat(BORDER_SATURATION, "%, ").concat(lightness, "%)")));
134
270
  if (candidate && (0, _styles.getContrastRatio)(averageHex, candidate) >= MIN_CONTRAST_RATIO) {
135
271
  return candidate;
@@ -160,11 +296,31 @@ var getBorderColorForAverage = exports.getBorderColorForAverage = function getBo
160
296
  * @param {function(string): ({r: number, g: number, b: number}|null|undefined)} getUnderlyingColor
161
297
  * @param {string} fallbackColor
162
298
  * @param {string} [mode] One of BorderColorModes
299
+ * @param {Array<{identifier: string}>} [designAreas] When set in contrast mode, one border is chosen for the pass
163
300
  * @return {function(object): string}
164
301
  */
165
- var createBorderColorResolver = exports.createBorderColorResolver = function createBorderColorResolver(getUnderlyingColor, fallbackColor, mode) {
302
+ var createBorderColorResolver = exports.createBorderColorResolver = function createBorderColorResolver(getUnderlyingColor, fallbackColor, mode, designAreas) {
303
+ var useUnifiedContrast = mode !== BorderColorModes.Complementary && designAreas && designAreas.length;
304
+ var unifiedBorderByIdentifier = null;
305
+ var getUnifiedBorderByIdentifier = function getUnifiedBorderByIdentifier() {
306
+ if (!useUnifiedContrast) {
307
+ return null;
308
+ }
309
+ var sampledCount = designAreas.filter(function (designArea) {
310
+ return (designArea === null || designArea === void 0 ? void 0 : designArea.identifier) && getUnderlyingColor(designArea.identifier);
311
+ }).length;
312
+ if (!unifiedBorderByIdentifier || sampledCount > 0 && unifiedBorderByIdentifier.size < sampledCount) {
313
+ unifiedBorderByIdentifier = computeUnifiedContrastBorders(getUnderlyingColor, designAreas);
314
+ }
315
+ return unifiedBorderByIdentifier;
316
+ };
166
317
  return function (designArea) {
167
- var average = getUnderlyingColor(designArea && designArea.identifier);
318
+ var identifier = designArea && designArea.identifier;
319
+ var unified = getUnifiedBorderByIdentifier();
320
+ if (unified && identifier && unified.has(identifier)) {
321
+ return unified.get(identifier);
322
+ }
323
+ var average = getUnderlyingColor(identifier);
168
324
  return average ? getBorderColorForAverage(average, mode) : fallbackColor;
169
325
  };
170
326
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@configuratorware/configurator-frontendgui",
3
- "version": "1.57.9",
3
+ "version": "1.57.11",
4
4
  "license": "UNLICENSED",
5
5
  "private": false,
6
6
  "main": "./index.js",
7
7
  "dependencies": {
8
8
  "@babel/polyfill": "^7.12.1",
9
- "@configuratorware/scripts": "1.57.9",
9
+ "@configuratorware/scripts": "1.57.11",
10
10
  "@material-ui/core": "^4.12.4",
11
11
  "@material-ui/icons": "^4.11.3",
12
12
  "@material-ui/lab": "^4.0.0-alpha.61",
@@ -36,8 +36,8 @@
36
36
  "react-router-dom": "^5.3.4",
37
37
  "react-swipeable": "^5.5.1",
38
38
  "react-zoom-pan-pinch": "^2.1.3",
39
- "redhotmagma-graphics-editor": "1.57.9",
40
- "redhotmagma-visualization": "1.57.9",
39
+ "redhotmagma-graphics-editor": "1.57.11",
40
+ "redhotmagma-visualization": "1.57.11",
41
41
  "redux": "^4.1.0",
42
42
  "redux-logger": "^3.0.6",
43
43
  "redux-persist": "^5.10.0",
@@ -64,7 +64,7 @@
64
64
  "css-loader": "^5.2.7",
65
65
  "file-loader": "^6.2.0",
66
66
  "mini-css-extract-plugin": "^2.7.6",
67
- "node-sass": "^9.0.0",
67
+ "sass": "1.32.0",
68
68
  "react": "^18.2.0",
69
69
  "react-dom": "^18.2.0",
70
70
  "react-refresh": "^0.14.0",
@@ -9,7 +9,7 @@ import uniq from 'lodash/uniq';
9
9
  import { t } from 'Framework/i18n';
10
10
  import Api from 'Framework/Api';
11
11
  import { createActionWithGlobalState } from 'Utils/Redux/createActionWithGlobalState';
12
- import { getConf } from '../../configuration';
12
+ import { getConf, setConf } from '../../configuration';
13
13
  import { Services } from '../../ServiceLocator';
14
14
  import {
15
15
  dismissError,
@@ -204,17 +204,29 @@ export const applyVariantAmountPreSelection = configurator => {
204
204
  getChildren(configurator).map(variant => Object.values(getDeepestChildIdentifiers(variant)))
205
205
  );
206
206
 
207
- for (const preSelectionVariant in itemAmounts) {
208
- if (!itemAmounts.hasOwnProperty(preSelectionVariant)) {
209
- continue;
210
- }
207
+ const applicableAmounts = Object.keys(itemAmounts)
208
+ .filter(identifier => children.indexOf(identifier) !== -1 && +itemAmounts[identifier] > 0)
209
+ .map(identifier => [identifier, +itemAmounts[identifier]]);
211
210
 
212
- const amount = +itemAmounts[preSelectionVariant];
211
+ // designerPreSelection is parsed once from the URL and stays in conf for the whole session, so
212
+ // without consuming it here it would re-apply (and re-clear existing amounts) on every later
213
+ // configuration load, e.g. switching the configuration variant or loading a saved configuration
214
+ setConf('designerPreSelection.itemAmounts', undefined);
213
215
 
214
- if (children.indexOf(preSelectionVariant) !== -1 && amount > 0) {
215
- Services.configurator.setSelectedAmount(preSelectionVariant, amount);
216
- }
216
+ if (applicableAmounts.length === 0) {
217
+ return;
217
218
  }
219
+
220
+ // only the explicitly passed amounts should apply, so clear whatever the item already had - by
221
+ // the keys actually selected, since a selected amount can be stored at any item level, not just
222
+ // on the deepest children
223
+ Object.keys(getSelectedAmounts(configurator) || {}).forEach(identifier =>
224
+ Services.configurator.setSelectedAmount(identifier, 0)
225
+ );
226
+
227
+ applicableAmounts.forEach(([identifier, amount]) =>
228
+ Services.configurator.setSelectedAmount(identifier, amount)
229
+ );
218
230
  }
219
231
  };
220
232
 
@@ -127,6 +127,127 @@ describe('Reducers/Configurator/Actions', () => {
127
127
  expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_red_s', 2);
128
128
  });
129
129
 
130
+ test('applyVariantAmountPreSelection clears the item default amount before applying the preselection', () => {
131
+ const mockConfigurator = {
132
+ configuration: {
133
+ item: {
134
+ itemGroup: {
135
+ children: [{ identifier: 'hoodie_white_default' }, { identifier: 'hoodie_black' }],
136
+ },
137
+ },
138
+ customdata: {
139
+ selectedAmounts: { hoodie_white_default: 1 },
140
+ },
141
+ },
142
+ };
143
+
144
+ const mockPreSelection = {
145
+ itemAmounts: {
146
+ hoodie_black: 50,
147
+ },
148
+ };
149
+
150
+ setConf('designerPreSelection', mockPreSelection);
151
+
152
+ applyVariantAmountPreSelection(mockConfigurator);
153
+
154
+ expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_white_default', 0);
155
+ expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_black', 50);
156
+ });
157
+
158
+ test('applyVariantAmountPreSelection clears a default amount stored at a non-leaf identifier', () => {
159
+ const mockConfigurator = {
160
+ configuration: {
161
+ item: {
162
+ itemGroup: {
163
+ children: [
164
+ {
165
+ identifier: 'hoodie_red',
166
+ itemGroup: {
167
+ children: [
168
+ { identifier: 'hoodie_red_s', itemgroupentryIdentifier: 's' },
169
+ { identifier: 'hoodie_red_m', itemgroupentryIdentifier: 'm' },
170
+ ],
171
+ },
172
+ },
173
+ ],
174
+ },
175
+ },
176
+ customdata: {
177
+ // the default amount is stored on the colour-level identifier, not a deepest child
178
+ selectedAmounts: { hoodie_red: 1 },
179
+ },
180
+ },
181
+ };
182
+
183
+ const mockPreSelection = {
184
+ itemAmounts: {
185
+ hoodie_red_s: 50,
186
+ },
187
+ };
188
+
189
+ setConf('designerPreSelection', mockPreSelection);
190
+
191
+ applyVariantAmountPreSelection(mockConfigurator);
192
+
193
+ expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_red', 0);
194
+ expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_red_s', 50);
195
+ });
196
+
197
+ test('applyVariantAmountPreSelection leaves existing amounts untouched when nothing in the preselection applies', () => {
198
+ const mockConfigurator = {
199
+ configuration: {
200
+ item: {
201
+ itemGroup: {
202
+ children: [{ identifier: 'hoodie_untouched' }],
203
+ },
204
+ },
205
+ customdata: {
206
+ selectedAmounts: { hoodie_untouched: 1 },
207
+ },
208
+ },
209
+ };
210
+
211
+ const mockPreSelection = {
212
+ itemAmounts: {
213
+ unknown_identifier: 50,
214
+ },
215
+ };
216
+
217
+ setConf('designerPreSelection', mockPreSelection);
218
+
219
+ const callsBefore = configuratorService.setSelectedAmount.mock.calls.length;
220
+
221
+ applyVariantAmountPreSelection(mockConfigurator);
222
+
223
+ expect(configuratorService.setSelectedAmount.mock.calls.length).toBe(callsBefore);
224
+ });
225
+
226
+ test('applyVariantAmountPreSelection only applies the preselection once', () => {
227
+ const mockConfigurator = {
228
+ configuration: {
229
+ item: {
230
+ itemGroup: {
231
+ children: [{ identifier: 'hoodie_once' }],
232
+ },
233
+ },
234
+ },
235
+ };
236
+
237
+ setConf('designerPreSelection', { itemAmounts: { hoodie_once: 7 } });
238
+
239
+ applyVariantAmountPreSelection(mockConfigurator);
240
+ expect(configuratorService.setSelectedAmount).toBeCalledWith('hoodie_once', 7);
241
+
242
+ const callsAfterFirstApply = configuratorService.setSelectedAmount.mock.calls.length;
243
+
244
+ // simulates a later configuration load (e.g. switching variant, loading a saved configuration) -
245
+ // designerPreSelection is still in conf, but its itemAmounts must have been consumed by now
246
+ applyVariantAmountPreSelection(mockConfigurator);
247
+
248
+ expect(configuratorService.setSelectedAmount.mock.calls.length).toBe(callsAfterFirstApply);
249
+ });
250
+
130
251
  describe('receiveConfiguration', () => {
131
252
  const configurator = {
132
253
  configuration: {
@@ -1604,16 +1604,28 @@ export default class DesignDataService {
1604
1604
  const showHighlight = get(state, 'designArea.showHighlight');
1605
1605
 
1606
1606
  const staticBorderColor = getClientHighlightColor(state);
1607
+ const dynamicDesignAreaHighlightBorderColor = getConf(
1608
+ 'visualization.dynamicDesignAreaHighlightBorderColor'
1609
+ );
1610
+ const dynamicDesignAreaHighlightBorderColorMode = getConf(
1611
+ 'visualization.dynamicDesignAreaHighlightBorderColorMode'
1612
+ );
1613
+ const getDesignAreaUnderlyingColor = identifier =>
1614
+ Services.visualization.getDesignAreaUnderlyingColor(identifier);
1615
+ // 2D invokes drawingFn once per area with a single-element list; unify borders from the full view.
1616
+ const allDesignAreasForBorderColor = this.getDesignAreas();
1617
+ const borderColor = dynamicDesignAreaHighlightBorderColor
1618
+ ? createBorderColorResolver(
1619
+ getDesignAreaUnderlyingColor,
1620
+ staticBorderColor,
1621
+ dynamicDesignAreaHighlightBorderColorMode,
1622
+ allDesignAreasForBorderColor
1623
+ )
1624
+ : staticBorderColor;
1607
1625
  const textureBuilderConfig = merge(
1608
1626
  {
1609
1627
  highlight: {
1610
- borderColor: getConf('visualization.dynamicDesignAreaHighlightBorderColor')
1611
- ? createBorderColorResolver(
1612
- identifier => Services.visualization.getDesignAreaUnderlyingColor(identifier),
1613
- staticBorderColor,
1614
- getConf('visualization.dynamicDesignAreaHighlightBorderColorMode')
1615
- )
1616
- : staticBorderColor,
1628
+ borderColor,
1617
1629
  backgroundColor: getConf('visualization.designAreaHighlightBackgroundColor'),
1618
1630
  lineWidth: getConf('visualization.designAreaHighlightBorderWidth', 2),
1619
1631
  },
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  BorderColorModes,
3
+ computeUnifiedContrastBorders,
3
4
  createBorderColorResolver,
4
5
  getBorderColorForAverage,
6
+ getContrastBorderColor,
5
7
  invertColor,
6
8
  rgbToHsl,
7
9
  } from '../getDesignAreaBorderColor';
@@ -96,6 +98,70 @@ describe('getBorderColorForAverage in complementary mode', () => {
96
98
  });
97
99
  });
98
100
 
101
+ const grey = value => ({ r: value, g: value, b: value });
102
+
103
+ describe('computeUnifiedContrastBorders', () => {
104
+ test('uses one border for similar greys that would flip individually', () => {
105
+ const colors = {
106
+ left: grey(100),
107
+ right: grey(120),
108
+ };
109
+ const borders = computeUnifiedContrastBorders(id => colors[id], [
110
+ { identifier: 'left' },
111
+ { identifier: 'right' },
112
+ ]);
113
+
114
+ expect(getContrastBorderColor('#646464')).toBe('#eeeeee');
115
+ expect(getContrastBorderColor('#787878')).toBe('#111111');
116
+ expect(borders.get('left')).toBe(borders.get('right'));
117
+ });
118
+
119
+ test('uses majority neutral when it is visible on every area', () => {
120
+ const colors = {
121
+ a: grey(50),
122
+ b: grey(60),
123
+ c: grey(128),
124
+ };
125
+ const borders = computeUnifiedContrastBorders(id => colors[id], [
126
+ { identifier: 'a' },
127
+ { identifier: 'b' },
128
+ { identifier: 'c' },
129
+ ]);
130
+
131
+ expect(borders.get('a')).toBe('#eeeeee');
132
+ expect(borders.get('b')).toBe('#eeeeee');
133
+ expect(borders.get('c')).toBe('#eeeeee');
134
+ });
135
+
136
+ test('falls back to maximin when majority neutral fails on an area', () => {
137
+ const colors = {
138
+ dark: grey(30),
139
+ mid: grey(128),
140
+ };
141
+ const borders = computeUnifiedContrastBorders(id => colors[id], [
142
+ { identifier: 'dark' },
143
+ { identifier: 'mid' },
144
+ ]);
145
+
146
+ expect(borders.get('dark')).toBe('#eeeeee');
147
+ expect(borders.get('mid')).toBe('#eeeeee');
148
+ });
149
+
150
+ test('allows per-area colors when no single neutral works everywhere', () => {
151
+ const colors = {
152
+ light: { r: 255, g: 255, b: 255 },
153
+ dark: { r: 0, g: 0, b: 0 },
154
+ };
155
+ const borders = computeUnifiedContrastBorders(id => colors[id], [
156
+ { identifier: 'light' },
157
+ { identifier: 'dark' },
158
+ ]);
159
+
160
+ expect(borders.get('light')).toBe('#111111');
161
+ expect(borders.get('dark')).toBe('#eeeeee');
162
+ });
163
+ });
164
+
99
165
  describe('createBorderColorResolver', () => {
100
166
  test('resolves the border color from the underlying color of the design area', () => {
101
167
  const getUnderlyingColor = jest.fn().mockReturnValue(JACKET_GREEN);
@@ -125,4 +191,75 @@ describe('createBorderColorResolver', () => {
125
191
  test('falls back to the given color without a design area', () => {
126
192
  expect(createBorderColorResolver(() => null, '#abcdef')()).toBe('#abcdef');
127
193
  });
194
+
195
+ test('unifies contrast borders across design areas when the list is given', () => {
196
+ const colors = {
197
+ left: grey(100),
198
+ right: grey(120),
199
+ };
200
+ const resolve = createBorderColorResolver(id => colors[id], '#abcdef', BorderColorModes.Contrast, [
201
+ { identifier: 'left' },
202
+ { identifier: 'right' },
203
+ ]);
204
+
205
+ expect(resolve({ identifier: 'left' })).toBe(resolve({ identifier: 'right' }));
206
+ });
207
+
208
+ test('unifies when resolved one area at a time like 2D drawing passes', () => {
209
+ const colors = {
210
+ chestLeft: grey(100),
211
+ chestRight: grey(120),
212
+ collar: grey(115),
213
+ };
214
+ const allAreas = [
215
+ { identifier: 'chestLeft' },
216
+ { identifier: 'chestRight' },
217
+ { identifier: 'collar' },
218
+ ];
219
+ const resolve = createBorderColorResolver(
220
+ id => colors[id],
221
+ '#abcdef',
222
+ BorderColorModes.Contrast,
223
+ allAreas
224
+ );
225
+
226
+ const chestLeftBorder = resolve({ identifier: 'chestLeft' });
227
+ const chestRightBorder = resolve({ identifier: 'chestRight' });
228
+ const collarBorder = resolve({ identifier: 'collar' });
229
+
230
+ expect(chestLeftBorder).toBe(chestRightBorder);
231
+ expect(chestRightBorder).toBe(collarBorder);
232
+ });
233
+
234
+ test('resolves per area when design areas are omitted', () => {
235
+ const resolve = createBorderColorResolver(
236
+ id => (id === 'left' ? grey(100) : grey(120)),
237
+ '#abcdef',
238
+ BorderColorModes.Contrast
239
+ );
240
+
241
+ expect(resolve({ identifier: 'left' })).toBe('#eeeeee');
242
+ expect(resolve({ identifier: 'right' })).toBe('#111111');
243
+ });
244
+
245
+ test('does not unify borders in complementary mode', () => {
246
+ const colors = {
247
+ left: grey(100),
248
+ right: grey(120),
249
+ };
250
+ const resolve = createBorderColorResolver(
251
+ id => colors[id],
252
+ '#abcdef',
253
+ BorderColorModes.Complementary,
254
+ [{ identifier: 'left' }, { identifier: 'right' }]
255
+ );
256
+
257
+ expect(resolve({ identifier: 'left' })).toBe(
258
+ getBorderColorForAverage(colors.left, BorderColorModes.Complementary)
259
+ );
260
+ expect(resolve({ identifier: 'right' })).toBe(
261
+ getBorderColorForAverage(colors.right, BorderColorModes.Complementary)
262
+ );
263
+ expect(resolve({ identifier: 'left' })).not.toBe(resolve({ identifier: 'right' }));
264
+ });
128
265
  });
@@ -95,6 +95,105 @@ export const getContrastBorderColor = averageHex =>
95
95
  ? NEUTRAL_DARK
96
96
  : NEUTRAL_LIGHT;
97
97
 
98
+ /**
99
+ * @param {string} neutral
100
+ * @param {Array<{averageHex: string}>} areas
101
+ * @return {number}
102
+ */
103
+ const minContrastForNeutralAcrossAreas = (neutral, areas) =>
104
+ Math.min(...areas.map(({ averageHex }) => getContrastRatio(averageHex, neutral)));
105
+
106
+ /**
107
+ * @param {string} neutral
108
+ * @param {Array<{averageHex: string}>} areas
109
+ * @return {boolean}
110
+ */
111
+ const neutralMeetsMinContrastOnAllAreas = (neutral, areas) =>
112
+ minContrastForNeutralAcrossAreas(neutral, areas) >= MIN_CONTRAST_RATIO;
113
+
114
+ /**
115
+ * Picks one near-black or near-white border for every design area in a highlight pass when possible,
116
+ * so similar product shades do not flip border color at the luminance tie-break.
117
+ * @param {function(string): ({r: number, g: number, b: number}|null|undefined)} getUnderlyingColor
118
+ * @param {Array<{identifier: string}>} designAreas
119
+ * @return {Map<string, string>}
120
+ */
121
+ export const computeUnifiedContrastBorders = (getUnderlyingColor, designAreas) => {
122
+ const areas = [];
123
+
124
+ for (const designArea of designAreas) {
125
+ const identifier = designArea && designArea.identifier;
126
+ if (!identifier) {
127
+ continue;
128
+ }
129
+ const average = getUnderlyingColor(identifier);
130
+ if (!average) {
131
+ continue;
132
+ }
133
+ areas.push({
134
+ identifier,
135
+ averageHex: rgbToHex(average.r, average.g, average.b),
136
+ });
137
+ }
138
+
139
+ const borderByIdentifier = new Map();
140
+
141
+ if (!areas.length) {
142
+ return borderByIdentifier;
143
+ }
144
+
145
+ let darkVotes = 0;
146
+ let lightVotes = 0;
147
+
148
+ for (const { averageHex } of areas) {
149
+ const vote = getContrastBorderColor(averageHex);
150
+ if (vote === NEUTRAL_DARK) {
151
+ darkVotes += 1;
152
+ } else {
153
+ lightVotes += 1;
154
+ }
155
+ }
156
+
157
+ let majorityNeutral;
158
+ if (darkVotes > lightVotes) {
159
+ majorityNeutral = NEUTRAL_DARK;
160
+ } else if (lightVotes > darkVotes) {
161
+ majorityNeutral = NEUTRAL_LIGHT;
162
+ }
163
+
164
+ if (majorityNeutral && neutralMeetsMinContrastOnAllAreas(majorityNeutral, areas)) {
165
+ for (const { identifier } of areas) {
166
+ borderByIdentifier.set(identifier, majorityNeutral);
167
+ }
168
+ return borderByIdentifier;
169
+ }
170
+
171
+ const qualifyingNeutrals = [NEUTRAL_DARK, NEUTRAL_LIGHT].filter(neutral =>
172
+ neutralMeetsMinContrastOnAllAreas(neutral, areas)
173
+ );
174
+
175
+ if (qualifyingNeutrals.length) {
176
+ const maximinNeutral = qualifyingNeutrals.reduce((best, neutral) => {
177
+ const minContrast = minContrastForNeutralAcrossAreas(neutral, areas);
178
+ if (!best) {
179
+ return { neutral, minContrast };
180
+ }
181
+ return minContrast > best.minContrast ? { neutral, minContrast } : best;
182
+ }, null).neutral;
183
+
184
+ for (const { identifier } of areas) {
185
+ borderByIdentifier.set(identifier, maximinNeutral);
186
+ }
187
+ return borderByIdentifier;
188
+ }
189
+
190
+ for (const { identifier, averageHex } of areas) {
191
+ borderByIdentifier.set(identifier, getContrastBorderColor(averageHex));
192
+ }
193
+
194
+ return borderByIdentifier;
195
+ };
196
+
98
197
  /**
99
198
  * Keeps the hue of the inverted (complementary) color at a high saturation and moves the lightness
100
199
  * away from the average, only as far as needed to reach a visible contrast, so the border is a vivid
@@ -150,9 +249,38 @@ export const getBorderColorForAverage = (average, mode = BorderColorModes.Contra
150
249
  * @param {function(string): ({r: number, g: number, b: number}|null|undefined)} getUnderlyingColor
151
250
  * @param {string} fallbackColor
152
251
  * @param {string} [mode] One of BorderColorModes
252
+ * @param {Array<{identifier: string}>} [designAreas] When set in contrast mode, one border is chosen for the pass
153
253
  * @return {function(object): string}
154
254
  */
155
- export const createBorderColorResolver = (getUnderlyingColor, fallbackColor, mode) => designArea => {
156
- const average = getUnderlyingColor(designArea && designArea.identifier);
157
- return average ? getBorderColorForAverage(average, mode) : fallbackColor;
255
+ export const createBorderColorResolver = (getUnderlyingColor, fallbackColor, mode, designAreas) => {
256
+ const useUnifiedContrast = mode !== BorderColorModes.Complementary && designAreas && designAreas.length;
257
+ let unifiedBorderByIdentifier = null;
258
+
259
+ const getUnifiedBorderByIdentifier = () => {
260
+ if (!useUnifiedContrast) {
261
+ return null;
262
+ }
263
+ const sampledCount = designAreas.filter(
264
+ designArea => designArea?.identifier && getUnderlyingColor(designArea.identifier)
265
+ ).length;
266
+ if (
267
+ !unifiedBorderByIdentifier ||
268
+ (sampledCount > 0 && unifiedBorderByIdentifier.size < sampledCount)
269
+ ) {
270
+ unifiedBorderByIdentifier = computeUnifiedContrastBorders(getUnderlyingColor, designAreas);
271
+ }
272
+ return unifiedBorderByIdentifier;
273
+ };
274
+
275
+ return designArea => {
276
+ const identifier = designArea && designArea.identifier;
277
+ const unified = getUnifiedBorderByIdentifier();
278
+
279
+ if (unified && identifier && unified.has(identifier)) {
280
+ return unified.get(identifier);
281
+ }
282
+
283
+ const average = getUnderlyingColor(identifier);
284
+ return average ? getBorderColorForAverage(average, mode) : fallbackColor;
285
+ };
158
286
  };