@configura/web-api 1.6.0-iotest.2 → 1.6.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/CfgProduct.d.ts +29 -10
  2. package/dist/CfgProduct.js +82 -47
  3. package/dist/index.d.ts +2 -4
  4. package/dist/index.js +2 -4
  5. package/dist/productConfiguration/CfgFeature.d.ts +2 -1
  6. package/dist/productConfiguration/CfgFeature.js +27 -8
  7. package/dist/productConfiguration/CfgOption.d.ts +21 -8
  8. package/dist/productConfiguration/CfgOption.js +26 -18
  9. package/dist/productConfiguration/CfgProductConfiguration.d.ts +1 -2
  10. package/dist/productConfiguration/CfgProductConfiguration.js +9 -8
  11. package/dist/syncGroups/SyncGroupsApplier.d.ts +20 -0
  12. package/dist/syncGroups/SyncGroupsApplier.js +520 -0
  13. package/dist/syncGroups/SyncGroupsApplyMode.d.ts +21 -0
  14. package/dist/syncGroups/SyncGroupsApplyMode.js +21 -0
  15. package/dist/syncGroups/SyncGroupsHandler.d.ts +31 -0
  16. package/dist/syncGroups/SyncGroupsHandler.js +71 -0
  17. package/dist/syncGroups/SyncGroupsPathHelper.d.ts +27 -0
  18. package/dist/syncGroups/SyncGroupsPathHelper.js +89 -0
  19. package/dist/syncGroups/SyncGroupsState.d.ts +20 -0
  20. package/dist/syncGroups/SyncGroupsState.js +108 -0
  21. package/dist/syncGroups/SyncGroupsTransaction.d.ts +51 -0
  22. package/dist/syncGroups/SyncGroupsTransaction.js +100 -0
  23. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +24 -95
  24. package/dist/tests/testData/testDataCachedGetProduct.js +8 -19
  25. package/dist/tests/testData/testDataOptions.d.ts +13 -0
  26. package/dist/tests/testData/testDataOptions.js +60 -0
  27. package/dist/tests/testData/testDataProductAggregatedPrice.js +12 -23
  28. package/dist/tests/testData/testDataUpcharge.js +16 -48
  29. package/dist/utilitiesCatalogueData.d.ts +7 -1
  30. package/dist/utilitiesCatalogueData.js +105 -4
  31. package/package.json +3 -3
  32. package/dist/io/CfgHistoryManager.d.ts +0 -30
  33. package/dist/io/CfgHistoryManager.js +0 -62
  34. package/dist/io/CfgHistoryToProdConfConnector.d.ts +0 -10
  35. package/dist/io/CfgHistoryToProdConfConnector.js +0 -20
  36. package/dist/io/CfgIOManager.d.ts +0 -29
  37. package/dist/io/CfgIOManager.js +0 -89
  38. package/dist/io/CfgIOProdConfConnector.d.ts +0 -33
  39. package/dist/io/CfgIOProdConfConnector.js +0 -100
  40. package/dist/io/CfgWindowMessageManager.d.ts +0 -13
  41. package/dist/io/CfgWindowMessageManager.js +0 -28
  42. package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +0 -13
  43. package/dist/io/CfgWindowMessageToProdConfConnector.js +0 -17
@@ -5,10 +5,11 @@ import { _CfgFeatureInternal } from "./productConfiguration/CfgFeature.js";
5
5
  import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
6
6
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
7
7
  import { ProductLoader } from "./productLoader.js";
8
+ import { SyncGroupsApplyMode } from "./syncGroups/SyncGroupsApplyMode.js";
9
+ import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
8
10
  import { CfgProductData, RootNodeSource } from "./utilitiesCatalogueData.js";
9
11
  export declare type CfgProductChangeNotification = {
10
12
  freshRef: CfgProduct;
11
- committed: boolean;
12
13
  };
13
14
  export declare type CfgProductSettings = {
14
15
  /**
@@ -25,6 +26,20 @@ export declare type CfgProductSettings = {
25
26
  * believe is a rare use case.
26
27
  */
27
28
  strictSetApiSelectionMatch: boolean;
29
+ /**
30
+ * Controls if SyncGroups are applied Faster or Stricter.
31
+ *
32
+ * Fast - Tries to minimize the number of validates calls to the AI lowering the response times
33
+ * for selecting options. Might not always give the expected result for complex products.
34
+ *
35
+ * Strict - Apply the SyncGroups rules in a stricter fashion to be as close to CET as possible,
36
+ * which might result in longer response times and more validation calls to the API when
37
+ * selecting options.
38
+ *
39
+ * The SDK will default to Strict, but we recommend that you try out Fast since cases where the
40
+ * results differ should be rare in most real uses cases and the speedup can be quite large.
41
+ */
42
+ syncGroupsApplyMode: SyncGroupsApplyMode | undefined;
28
43
  };
29
44
  /**
30
45
  * This enum is used internally in the SDK and is not expected by be used directly by integrators.
@@ -49,6 +64,10 @@ export declare type CfgPrice = {
49
64
  currency: string;
50
65
  fractionDigits: number;
51
66
  };
67
+ export declare type RevalidateResult = {
68
+ wasAborted: boolean;
69
+ requestDidValidate: boolean;
70
+ };
52
71
  /**
53
72
  * This class is meant to only be used through CfgProduct. It should never be instantiated on its
54
73
  * own. Normally the internal state of this class should never be directly modified. CfgProduct is
@@ -69,6 +88,7 @@ export declare class _CfgProductInternal {
69
88
  readonly parent: _CfgProductInternal | undefined;
70
89
  readonly transform: Transform | undefined;
71
90
  anchor: MeasureParam | undefined;
91
+ private readonly _syncGroupHandler;
72
92
  static make: (productLoaderRaw: ProductLoader, productLoaderForGroupedLoad: ProductLoader | undefined, lang: string, catId: CatalogueParams, partNumber: string, settings: CfgProductSettings, optional: boolean, loadingObservable: AggregatedLoadingObservable, refKey: string | undefined, refDescription: string | undefined, parent: _CfgProductInternal | undefined, root: _CfgProductInternal | undefined, transform: Transform | undefined, anchor: MeasureParam | undefined) => Promise<_CfgProductInternal>;
73
93
  private constructor();
74
94
  readonly root: _CfgProductInternal;
@@ -113,17 +133,18 @@ export declare class _CfgProductInternal {
113
133
  * It does not affect the visibility of anything in the 3D view at all.
114
134
  */
115
135
  get visible(): boolean;
116
- _notifyAllOfChange: (bubbleMode: CfgProductBubbleMode, committed: boolean) => Promise<void>;
136
+ _notifyAllOfChange: (bubbleMode: CfgProductBubbleMode) => Promise<void>;
117
137
  /** Called when a child (additional product or the configuration) has changed. */
118
138
  private _childHasChanged;
119
139
  /** Called by child to tell its parent that it has changed. */
120
- _additionalProductHasChanged: (freshRef: CfgProduct, bubbleMode: CfgProductBubbleMode, committed: boolean) => Promise<void>;
140
+ _additionalProductHasChanged: (freshRef: CfgProduct, bubbleMode: CfgProductBubbleMode) => Promise<void>;
121
141
  /** Called by the configuration to tell its parent that it has changed. */
122
- _configurationHasChanged: (freshRef: CfgProductConfiguration, bubbleMode: ProductConfigurationBubbleMode, committed: boolean) => Promise<void>;
142
+ _configurationHasChanged: (freshRef: CfgProductConfiguration, bubbleMode: ProductConfigurationBubbleMode) => Promise<void>;
123
143
  getApiSelection: () => AdditionalProductConfiguration;
124
144
  setApiSelection: (s: AdditionalProductConfiguration, doValidate: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
125
- getApiSelectionAsString: () => string;
126
- setFromApiSelectionString: (str: string, doValidate: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
145
+ copyFrom: (otherProduct: _CfgProductInternal, doValidate: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
146
+ private _setApiSelectionWithOtherProduct;
147
+ get syncGroupHandler(): SyncGroupsHandler | undefined;
127
148
  structureCompare: (other: _CfgProductInternal, strictOrder?: boolean, descriptionMatch?: boolean) => boolean;
128
149
  tryMatchSelection: (other: _CfgProductInternal, descriptionMatch?: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
129
150
  /** Only features in selected options and selected additional products. */
@@ -134,7 +155,7 @@ export declare class _CfgProductInternal {
134
155
  * product in isolation. The validation result is applied on the configuration. Then additional
135
156
  * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
136
157
  */
137
- _revalidate: (bubbleMode: CfgProductBubbleMode, productLoader: ProductLoader, committed: boolean) => Promise<boolean>;
158
+ _revalidate: (bubbleMode: CfgProductBubbleMode, productLoader: ProductLoader) => Promise<RevalidateResult>;
138
159
  /**
139
160
  * Based on this configuration find what additional products should be shown and not, unload
140
161
  * (i.e. destroy) those that should no longer be shown, load the new ones.
@@ -146,7 +167,7 @@ export declare class CfgProduct {
146
167
  static make(productLoader: ProductLoader, lang: string, catId: CatalogueParams, partNumber: string, settings?: Partial<CfgProductSettings>): Promise<CfgProduct>;
147
168
  /**
148
169
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to
149
- * make a new outer reference. Like a shallow copy./ We use this to help frameworks that are
170
+ * make a new outer reference. Like a shallow copy. We use this to help frameworks that are
150
171
  * build around using equals to detect change.
151
172
  */
152
173
  static _makeNewRefFrom(source: _CfgProductInternal): CfgProduct;
@@ -233,8 +254,6 @@ export declare class CfgProduct {
233
254
  */
234
255
  getApiSelection: () => AdditionalProductConfiguration;
235
256
  setApiSelection: (s: AdditionalProductConfiguration, doValidate?: boolean) => Promise<boolean>;
236
- getApiSelectionAsString: () => string;
237
- setFromApiSelectionString: (str: string, doValidate?: boolean) => Promise<boolean>;
238
257
  listenForChange: (l: SingleArgCallback<CfgProductChangeNotification>) => void;
239
258
  stopListenForChange: (l: SingleArgCallback<CfgProductChangeNotification>) => void;
240
259
  stopAllListenForChange: () => void;
@@ -7,18 +7,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { AggregatedLoadingObservable, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
10
+ import { AggregatedLoadingObservable, assert, assertDefined, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
12
12
  import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
13
13
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
14
14
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
15
15
  import { wrapWithCache } from "./productLoader.js";
16
- import { comparePricesObjects, correctDefaultsOnCatalogueParams, makeProductKey, } from "./utilitiesCatalogueData.js";
16
+ import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
17
+ import { comparePricesObjects, correctDefaultsOnCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
17
18
  function completeSettings(incompleteSettings) {
18
19
  var _a, _b;
19
20
  return {
20
21
  strictSelectOneSelectionCount: (_a = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSelectOneSelectionCount) !== null && _a !== void 0 ? _a : false,
21
22
  strictSetApiSelectionMatch: (_b = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSetApiSelectionMatch) !== null && _b !== void 0 ? _b : false,
23
+ syncGroupsApplyMode: incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.syncGroupsApplyMode,
22
24
  };
23
25
  }
24
26
  /**
@@ -50,7 +52,7 @@ function isDescriptionMatch(l, r) {
50
52
  * the class that should be used and interacted with.
51
53
  */
52
54
  export class _CfgProductInternal {
53
- constructor(initSuccess, initFail, _productLoaderRaw, lang, catId, partNumber, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, refKey, refDescription, parent, root, transform, anchor) {
55
+ constructor(initSuccess, initFail, _productLoaderRaw, lang, catId, partNumber, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, refKey, refDescription, parent, root, transform, anchor, _syncGroupHandler) {
54
56
  this._productLoaderRaw = _productLoaderRaw;
55
57
  this.lang = lang;
56
58
  this.catId = catId;
@@ -65,6 +67,7 @@ export class _CfgProductInternal {
65
67
  this.parent = parent;
66
68
  this.transform = transform;
67
69
  this.anchor = anchor;
70
+ this._syncGroupHandler = _syncGroupHandler;
68
71
  this._destroyed = false;
69
72
  this.additionalProducts = [];
70
73
  this.changeObservable = new Observable();
@@ -76,31 +79,31 @@ export class _CfgProductInternal {
76
79
  additionalProduct.destroy();
77
80
  }
78
81
  };
79
- this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
82
+ this._notifyAllOfChange = (bubbleMode) => __awaiter(this, void 0, void 0, function* () {
80
83
  if (bubbleMode === CfgProductBubbleMode.Stop) {
81
84
  return;
82
85
  }
83
86
  const parent = this.parent;
84
87
  const freshRef = CfgProduct._makeNewRefFrom(this);
85
- this.changeObservable.notifyAll({ freshRef, committed });
88
+ this.changeObservable.notifyAll({ freshRef });
86
89
  if (parent !== undefined) {
87
90
  yield parent._additionalProductHasChanged(freshRef, bubbleMode === CfgProductBubbleMode.OneLevel
88
91
  ? CfgProductBubbleMode.Stop
89
- : bubbleMode, committed);
92
+ : bubbleMode);
90
93
  }
91
94
  });
92
95
  /** Called when a child (additional product or the configuration) has changed. */
93
- this._childHasChanged = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
96
+ this._childHasChanged = (bubbleMode) => __awaiter(this, void 0, void 0, function* () {
94
97
  if (bubbleMode === CfgProductBubbleMode.ToRootAndBubbleSelected &&
95
98
  this.optional &&
96
99
  !this.selected) {
97
100
  yield this.setSelected(true, bubbleMode);
98
101
  return;
99
102
  }
100
- yield this._notifyAllOfChange(bubbleMode, committed);
103
+ yield this._notifyAllOfChange(bubbleMode);
101
104
  });
102
105
  /** Called by child to tell its parent that it has changed. */
103
- this._additionalProductHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
106
+ this._additionalProductHasChanged = (freshRef, bubbleMode) => __awaiter(this, void 0, void 0, function* () {
104
107
  const i = this.additionalProducts.findIndex((a) => a.isBackedBySame(freshRef));
105
108
  if (i !== -1) {
106
109
  // Child additional product might not be found. This probably means that propagate
@@ -110,31 +113,34 @@ export class _CfgProductInternal {
110
113
  // C no longer is part of the tree. Odd, but fully permitted.
111
114
  this.additionalProducts[i] = freshRef;
112
115
  }
113
- yield this._childHasChanged(bubbleMode, committed);
116
+ yield this._childHasChanged(bubbleMode);
114
117
  });
115
118
  /** Called by the configuration to tell its parent that it has changed. */
116
- this._configurationHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
119
+ this._configurationHasChanged = (freshRef, bubbleMode) => __awaiter(this, void 0, void 0, function* () {
117
120
  this._configuration = freshRef;
118
121
  switch (bubbleMode) {
119
122
  case ProductConfigurationBubbleMode.ValidateAndBubbleSelected:
120
123
  // The revalidate call will continue the bubble
121
- yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw, committed);
124
+ yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw);
125
+ return;
126
+ case ProductConfigurationBubbleMode.BubbleSelected:
127
+ yield this._childHasChanged(CfgProductBubbleMode.ToRootAndBubbleSelected);
122
128
  return;
123
129
  case ProductConfigurationBubbleMode.Validate:
124
130
  // The revalidate call will continue the bubble
125
- yield this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw, committed);
131
+ yield this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw);
126
132
  return;
127
133
  case ProductConfigurationBubbleMode.ToParentProduct:
128
134
  // Do not continue bubble as we have reached the parent CfgProduct
129
135
  return;
130
136
  case ProductConfigurationBubbleMode.OneLevel:
131
- yield this._childHasChanged(CfgProductBubbleMode.OneLevel, committed);
137
+ yield this._childHasChanged(CfgProductBubbleMode.OneLevel);
132
138
  return;
133
139
  case ProductConfigurationBubbleMode.Stop:
134
- yield this._childHasChanged(CfgProductBubbleMode.Stop, committed);
140
+ yield this._childHasChanged(CfgProductBubbleMode.Stop);
135
141
  return;
136
142
  case ProductConfigurationBubbleMode.ToRoot:
137
- yield this._childHasChanged(CfgProductBubbleMode.ToRoot, committed);
143
+ yield this._childHasChanged(CfgProductBubbleMode.ToRoot);
138
144
  return;
139
145
  }
140
146
  });
@@ -145,13 +151,27 @@ export class _CfgProductInternal {
145
151
  additionalProducts: this.additionalProducts.map((additionalProduct) => additionalProduct.getApiSelection()),
146
152
  });
147
153
  this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
154
+ return this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
155
+ });
156
+ this.copyFrom = (otherProduct, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
157
+ return yield this._setApiSelectionWithOtherProduct(otherProduct.getApiSelection(), doValidate, productLoaderForGroupedLoad, otherProduct);
158
+ });
159
+ this._setApiSelectionWithOtherProduct = (s, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
148
160
  // Wrap with cache will make getProduct for this function call use the same server call
149
161
  // for the same product with the same params. Used for getProduct (when a new additional
150
162
  // product is loaded) and postValidate.
151
163
  productLoaderForGroupedLoad =
152
164
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
165
+ let change = false;
166
+ if (sourceProduct !== undefined) {
167
+ this._rawProductData = sourceProduct.rawProductData;
168
+ this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs);
169
+ change = true; // We can not know if this is an actual change, so we assume it is
170
+ }
153
171
  const configurationChange = yield this.configuration._internal.setApiSelection(s.selOptions, false);
154
- let change = configurationChange;
172
+ if (configurationChange) {
173
+ change = true;
174
+ }
155
175
  if (this.optional) {
156
176
  if (yield this.setSelected(s.selected !== false, CfgProductBubbleMode.Stop)) {
157
177
  change = true;
@@ -165,35 +185,37 @@ export class _CfgProductInternal {
165
185
  if (apiSelectionAdditionalProductsCount !== additionalProductsCount) {
166
186
  throw new Error(`Additional products are not same length. This product: "${this.key}". This product additional products count: ${additionalProductsCount}. Passed apiSelection additional products count: ${apiSelectionAdditionalProductsCount}`);
167
187
  }
168
- if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionChild) => {
169
- const refKey = apiSelectionChild.refKey;
170
- if (refKey === undefined) {
171
- throw new Error("Additional product api configurations must have refKey.");
172
- }
188
+ const sourceProductAdditionalProducts = sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct.additionalProducts;
189
+ assert(!sourceProductAdditionalProducts ||
190
+ additionalProductsCount === sourceProductAdditionalProducts.length, `Passed sourceProduct does not have the same number of additional products as this.`);
191
+ if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct, index) => {
192
+ var _a;
193
+ const refKey = apiSelectionAdditionalProduct.refKey;
194
+ assertDefined(refKey, "Additional product api configurations must have refKey.");
173
195
  const i = additionalProducts.findIndex((a) => refKey === a.refKey);
174
- if (i === -1) {
175
- throw new Error(`Additional product not found. This product: "${this.key}". refKey of not found additional product: "${refKey}"`);
196
+ assert(i !== -1, `Additional product not found. This product: "${this.key}". refKey of not found additional product: "${refKey}"`);
197
+ let sourceProductAdditionalProduct = undefined;
198
+ if (sourceProductAdditionalProducts !== undefined) {
199
+ sourceProductAdditionalProduct =
200
+ (_a = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _a === void 0 ? void 0 : _a._internal;
201
+ assertDefined(sourceProductAdditionalProduct, "Additional product not found in sourceProduct");
176
202
  }
177
203
  const additionalProduct = additionalProducts.splice(i, 1)[0]; // Splicing like this is okay because this is done synchronous. The setCon. is what is async.
178
- return additionalProduct._internal.setApiSelection(apiSelectionChild, doValidate, productLoaderForGroupedLoad);
204
+ return additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
179
205
  }))).some((b) => b)) {
180
206
  change = true;
181
207
  }
182
208
  if (doValidate && configurationChange) {
183
- yield this._revalidate(CfgProductBubbleMode.ToRoot, productLoaderForGroupedLoad, true);
209
+ yield this._revalidate(CfgProductBubbleMode.ToRoot, productLoaderForGroupedLoad);
184
210
  }
185
211
  else if (change) {
186
212
  // As setApiSelection is done recursively each level takes care of its own notifications
187
213
  // so we only need to bubble one level to notify this and swap out the reference in the
188
214
  // parent.
189
- yield this._notifyAllOfChange(CfgProductBubbleMode.OneLevel, true);
215
+ yield this._notifyAllOfChange(CfgProductBubbleMode.OneLevel);
190
216
  }
191
217
  return change;
192
218
  });
193
- this.getApiSelectionAsString = () => JSON.stringify(this.getApiSelection(), undefined, "");
194
- this.setFromApiSelectionString = (str, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
195
- return this.setApiSelection(JSON.parse(decodeURIComponent(str)), doValidate, productLoaderForGroupedLoad);
196
- });
197
219
  this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => {
198
220
  if (!this.configuration.structureCompare(other.configuration, strictOrder, descriptionMatch)) {
199
221
  return false;
@@ -216,10 +238,10 @@ export class _CfgProductInternal {
216
238
  const configurationChange = yield this.configuration._internal.tryMatchSelection(other.configuration._internal, descriptionMatch, false);
217
239
  if (configurationChange) {
218
240
  change = true;
219
- yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, productLoaderForGroupedLoad, true);
241
+ yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, productLoaderForGroupedLoad);
220
242
  }
221
243
  else if (change) {
222
- yield this._notifyAllOfChange(CfgProductBubbleMode.ToRootAndBubbleSelected, true);
244
+ yield this._notifyAllOfChange(CfgProductBubbleMode.ToRootAndBubbleSelected);
223
245
  }
224
246
  const thisAdditionalProducts = this.additionalProducts;
225
247
  const otherAdditionalProducts = other.additionalProducts;
@@ -268,12 +290,13 @@ export class _CfgProductInternal {
268
290
  * product in isolation. The validation result is applied on the configuration. Then additional
269
291
  * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
270
292
  */
271
- this._revalidate = (bubbleMode, productLoader, committed) => __awaiter(this, void 0, void 0, function* () {
293
+ this._revalidate = (bubbleMode, productLoader) => __awaiter(this, void 0, void 0, function* () {
272
294
  const { _configuration: configuration } = this;
273
295
  const token = this.loadingObservable.startChildLoading();
274
296
  this._revalidateInProgressToken = token;
275
297
  try {
276
298
  const response = yield productLoader.postValidate(Object.assign(Object.assign({ lang: this.lang }, correctDefaultsOnCatalogueParams(this.catId)), { partNumber: this.partNumber }), { selOptions: configuration.getApiSelection() });
299
+ const requestDidValidate = response.validated;
277
300
  // The revalidateInProgressToken is used to know if some other revalidate
278
301
  // call has happened after this call, thereby making this call obsolete.
279
302
  // This is a bit crude in that it does not actually cancel previous validate
@@ -282,7 +305,7 @@ export class _CfgProductInternal {
282
305
  // of all, the heavy work happens on the server, and that work will not be
283
306
  // cancelled even if we would cancel the call.
284
307
  if (this._revalidateInProgressToken !== token) {
285
- return false;
308
+ return { wasAborted: true, requestDidValidate };
286
309
  }
287
310
  // After a successful validate-call we will always assume there
288
311
  // is a change. It would be possible to compare relevant parts
@@ -290,7 +313,7 @@ export class _CfgProductInternal {
290
313
  // syndAndLoad, however the code comparing productData would be fragile
291
314
  // and likely to break if new data-fields were added.
292
315
  if (this._destroyed) {
293
- return false;
316
+ return { wasAborted: true, requestDidValidate };
294
317
  }
295
318
  const { productData, rootFeatureRefs } = response;
296
319
  const pricesUpdated = !comparePricesObjects(this.prices, productData.partsData.prices);
@@ -305,10 +328,10 @@ export class _CfgProductInternal {
305
328
  yield configuration._internal.setApiSelection(productData.partsData.selOptions || [], false);
306
329
  yield this._syncAndLoadAdditionalProducts(productLoader);
307
330
  if (this._destroyed) {
308
- return false;
331
+ return { wasAborted: true, requestDidValidate };
309
332
  }
310
- yield this._notifyAllOfChange(bubbleMode, committed);
311
- return true;
333
+ yield this._notifyAllOfChange(bubbleMode);
334
+ return { wasAborted: false, requestDidValidate };
312
335
  }
313
336
  catch (e) {
314
337
  throw e;
@@ -326,7 +349,14 @@ export class _CfgProductInternal {
326
349
  const additionalProductRefs = [
327
350
  ...(productData.additionalProductRefs || []),
328
351
  ...collectAdditionalProductRefs(configuration),
329
- ].map((prodRef, originalIndex) => ({
352
+ ]
353
+ .reduce((a, c) => {
354
+ if (a.every((p) => !isSameProductRef(p, c))) {
355
+ a.push(c);
356
+ }
357
+ return a;
358
+ }, [])
359
+ .map((prodRef, originalIndex) => ({
330
360
  prodRef,
331
361
  originalIndex,
332
362
  }));
@@ -372,7 +402,7 @@ export class _CfgProductInternal {
372
402
  this.loadingObservable.stopChildLoading(token);
373
403
  }
374
404
  });
375
- this.root = root || this;
405
+ this.root = root !== null && root !== void 0 ? root : this;
376
406
  this.key = makeProductKey(catId, refKey || partNumber);
377
407
  this._selected = optional ? selected : undefined;
378
408
  this.isAdditionalProduct = parent !== undefined;
@@ -384,9 +414,10 @@ export class _CfgProductInternal {
384
414
  clone(parent, root) {
385
415
  return __awaiter(this, void 0, void 0, function* () {
386
416
  const product = yield new Promise((initSuccess, initFail) => {
417
+ var _a;
387
418
  const p = new _CfgProductInternal(() => {
388
419
  initSuccess(p);
389
- }, initFail, this._productLoaderRaw, this.lang, this.catId, this.partNumber, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.allRawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration.getApiSelection(), new AggregatedLoadingObservable(), this.refKey, this.refDescription, parent, root, this.transform, this.anchor);
420
+ }, initFail, this._productLoaderRaw, this.lang, this.catId, this.partNumber, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.allRawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration.getApiSelection(), new AggregatedLoadingObservable(), this.refKey, this.refDescription, parent, root, this.transform, this.anchor, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
390
421
  });
391
422
  for (const additionalProduct of this.additionalProducts) {
392
423
  product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
@@ -465,7 +496,7 @@ export class _CfgProductInternal {
465
496
  return false;
466
497
  }
467
498
  this._selected = v;
468
- yield this._notifyAllOfChange(bubbleMode, true);
499
+ yield this._notifyAllOfChange(bubbleMode);
469
500
  return true;
470
501
  });
471
502
  }
@@ -498,6 +529,9 @@ export class _CfgProductInternal {
498
529
  ? this.visibleIfAdditionalProduct
499
530
  : this.visibleIfMainProduct;
500
531
  }
532
+ get syncGroupHandler() {
533
+ return this.root._syncGroupHandler;
534
+ }
501
535
  }
502
536
  _CfgProductInternal.make = (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
503
537
  lang, catId, partNumber, settings, optional, loadingObservable, refKey, refDescription, parent, root, transform, anchor) => __awaiter(void 0, void 0, void 0, function* () {
@@ -506,6 +540,7 @@ lang, catId, partNumber, settings, optional, loadingObservable, refKey, refDescr
506
540
  // at this initial load.
507
541
  productLoaderForGroupedLoad =
508
542
  productLoaderForGroupedLoad || wrapWithCache(productLoaderRaw);
543
+ const syncGroupHandler = root === undefined ? SyncGroupsHandler.make(settings.syncGroupsApplyMode) : undefined;
509
544
  const productResponse = yield productLoaderForGroupedLoad.getProduct(Object.assign(Object.assign({ lang }, correctDefaultsOnCatalogueParams(catId)), { partNumber }));
510
545
  const { productData, rootFeatureRefs, features: allRawFeatures, uuid, unit, } = productResponse;
511
546
  const product = yield new Promise((initSuccess, initFail) => {
@@ -514,9 +549,11 @@ lang, catId, partNumber, settings, optional, loadingObservable, refKey, refDescr
514
549
  // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
515
550
  // Feel free to find a nicer more readable solution :)
516
551
  initSuccess(p);
517
- }, initFail, productLoaderRaw, lang, catId, partNumber, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, refKey, refDescription, parent, root, transform, anchor);
552
+ }, initFail, productLoaderRaw, lang, catId, partNumber, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, refKey, refDescription, parent, root, transform, anchor, syncGroupHandler);
518
553
  });
519
554
  yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
555
+ // Product is guaranteed to be root
556
+ yield (syncGroupHandler === null || syncGroupHandler === void 0 ? void 0 : syncGroupHandler.init(product, productLoaderForGroupedLoad));
520
557
  return product;
521
558
  });
522
559
  export class CfgProduct {
@@ -558,8 +595,6 @@ export class CfgProduct {
558
595
  */
559
596
  this.getApiSelection = () => this._internal.getApiSelection();
560
597
  this.setApiSelection = (s, doValidate = false) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(s, doValidate); });
561
- this.getApiSelectionAsString = () => this._internal.getApiSelectionAsString();
562
- this.setFromApiSelectionString = (str, doValidate = false) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setFromApiSelectionString(str, doValidate); });
563
598
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
564
599
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
565
600
  this.stopAllListenForChange = () => this._internal.changeObservable.stopAllListen();
@@ -574,7 +609,7 @@ export class CfgProduct {
574
609
  }
575
610
  /**
576
611
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to
577
- * make a new outer reference. Like a shallow copy./ We use this to help frameworks that are
612
+ * make a new outer reference. Like a shallow copy. We use this to help frameworks that are
578
613
  * build around using equals to detect change.
579
614
  */
580
615
  static _makeNewRefFrom(source) {
package/dist/index.d.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  export * from "./CatalogueAPI.js";
2
2
  export * from "./CfgProduct.js";
3
- export * from "./io/CfgHistoryManager.js";
4
- export * from "./io/CfgHistoryToProdConfConnector.js";
5
- export * from "./io/CfgWindowMessageManager.js";
6
- export * from "./io/CfgWindowMessageToProdConfConnector.js";
7
3
  export * from "./material/CfgMaterialMapping.js";
8
4
  export * from "./material/CfgMtrlApplication.js";
9
5
  export * from "./material/CfgMtrlApplicationSource.js";
@@ -15,6 +11,8 @@ export * from "./productConfiguration/CfgProductConfiguration.js";
15
11
  export * from "./productConfiguration/filters.js";
16
12
  export * from "./productConfiguration/productParamsGenerator.js";
17
13
  export * from "./productLoader.js";
14
+ export * from "./syncGroups/SyncGroupsApplyMode.js";
15
+ export * from "./syncGroups/SyncGroupsHandler.js";
18
16
  export * from "./tasks/formats.js";
19
17
  export * from "./tasks/TaskHandler.js";
20
18
  export * from "./utilitiesCatalogueData.js";
package/dist/index.js CHANGED
@@ -1,9 +1,5 @@
1
1
  export * from "./CatalogueAPI.js";
2
2
  export * from "./CfgProduct.js";
3
- export * from "./io/CfgHistoryManager.js";
4
- export * from "./io/CfgHistoryToProdConfConnector.js";
5
- export * from "./io/CfgWindowMessageManager.js";
6
- export * from "./io/CfgWindowMessageToProdConfConnector.js";
7
3
  export * from "./material/CfgMaterialMapping.js";
8
4
  export * from "./material/CfgMtrlApplication.js";
9
5
  export * from "./material/CfgMtrlApplicationSource.js";
@@ -15,6 +11,8 @@ export * from "./productConfiguration/CfgProductConfiguration.js";
15
11
  export * from "./productConfiguration/filters.js";
16
12
  export * from "./productConfiguration/productParamsGenerator.js";
17
13
  export * from "./productLoader.js";
14
+ export * from "./syncGroups/SyncGroupsApplyMode.js";
15
+ export * from "./syncGroups/SyncGroupsHandler.js";
18
16
  export * from "./tasks/formats.js";
19
17
  export * from "./tasks/TaskHandler.js";
20
18
  export * from "./utilitiesCatalogueData.js";
@@ -1,5 +1,5 @@
1
1
  import { LengthUnit, Observable, SingleArgCallback } from "@configura/web-utilities";
2
- import { Feature, SelectedOption } from "../CatalogueAPI.js";
2
+ import { Feature, SelectedOption, SyncGroup } from "../CatalogueAPI.js";
3
3
  import { CfgProduct, _CfgProductInternal } from "../CfgProduct.js";
4
4
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
5
5
  import { CfgOption, ProductConfigurationBubbleMode, _CfgOptionInternal } from "./CfgOption.js";
@@ -49,6 +49,7 @@ export declare class _CfgFeatureInternal {
49
49
  get isUseNumericValue(): boolean;
50
50
  get numericValue(): number | undefined;
51
51
  setNumericValue: (val: number) => Promise<boolean>;
52
+ get syncGroup(): SyncGroup | undefined;
52
53
  get description(): string;
53
54
  /**
54
55
  * The MeasureParam class is re-used for different purposes. In Features it is used
@@ -7,10 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { compareArrays, convertLength, count, Observable, someMatch, toLengthUnit, } from "@configura/web-utilities";
10
+ import { assertDefined, compareArrays, convertLength, count, Observable, someMatch, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgProduct } from "../CfgProduct.js";
12
12
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
13
13
  import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
14
+ import { wrapWithCache } from "../productLoader.js";
15
+ import { SyncGroupsPathHelper } from "../syncGroups/SyncGroupsPathHelper.js";
14
16
  import { CfgOption, ProductConfigurationBubbleMode } from "./CfgOption.js";
15
17
  import { _CfgProductConfigurationInternal } from "./CfgProductConfiguration.js";
16
18
  import { getMtrlPreview } from "./utilitiesProductConfiguration.js";
@@ -86,9 +88,9 @@ export class _CfgFeatureInternal {
86
88
  if (bubbleMode === ProductConfigurationBubbleMode.Stop) {
87
89
  return;
88
90
  }
91
+ const parent = this.parent;
89
92
  const freshRef = CfgFeature._makeNewRefFrom(this);
90
93
  this.changeObservable.notifyAll({ freshRef });
91
- const parent = this.parent;
92
94
  if (parent !== undefined) {
93
95
  yield parent._childHasChanged(freshRef, bubbleMode === ProductConfigurationBubbleMode.OneLevel
94
96
  ? ProductConfigurationBubbleMode.Stop
@@ -306,6 +308,13 @@ export class _CfgFeatureInternal {
306
308
  * Using a validate bubbleMode will cause validation calls to the server.
307
309
  */
308
310
  this.selectOption = (optionInternal, on, bubbleMode) => __awaiter(this, void 0, void 0, function* () {
311
+ if (bubbleMode ===
312
+ ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups) {
313
+ const product = this.rootProduct;
314
+ const syncGroupHandler = product.syncGroupHandler;
315
+ assertDefined(syncGroupHandler, `Sync group handler is required for bubble mode ${ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups}`);
316
+ return yield syncGroupHandler.selectOption(product, SyncGroupsPathHelper.getPath(optionInternal), on, wrapWithCache(product._productLoaderRaw));
317
+ }
309
318
  if (!on) {
310
319
  if (this.selectionType === SelectionType.Group) {
311
320
  throw new Error(`Multiple features are always selected and are not user selectable. Feature key: "${this.key}".`);
@@ -357,11 +366,18 @@ export class _CfgFeatureInternal {
357
366
  if (isAllOptionsAffectedByAnySelection) {
358
367
  this._freshRefAllOptions();
359
368
  }
360
- // If this was a deselect action we shall not bubble selected, that is, we shall not
361
- // select the ancestors if this action was deselect.
362
- yield this._notifyAllOfChange(!on && bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected
363
- ? ProductConfigurationBubbleMode.Validate
364
- : bubbleMode);
369
+ let nextLevelBubbleMode = bubbleMode;
370
+ if (!on) {
371
+ // If this was a deselect action we shall not bubble selected, that is, we shall not
372
+ // select the ancestors if this action was deselect.
373
+ if (bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected) {
374
+ nextLevelBubbleMode = ProductConfigurationBubbleMode.Validate;
375
+ }
376
+ else if (bubbleMode === ProductConfigurationBubbleMode.BubbleSelected) {
377
+ nextLevelBubbleMode = ProductConfigurationBubbleMode.ToRoot;
378
+ }
379
+ }
380
+ yield this._notifyAllOfChange(nextLevelBubbleMode);
365
381
  return isActualChange;
366
382
  });
367
383
  this.isSelected = (option) => this.selectionType === SelectionType.Group ||
@@ -412,6 +428,9 @@ export class _CfgFeatureInternal {
412
428
  }
413
429
  return value;
414
430
  }
431
+ get syncGroup() {
432
+ return this.rawFeature.syncGroup;
433
+ }
415
434
  get description() {
416
435
  return this.rawFeature.description;
417
436
  }
@@ -531,7 +550,7 @@ export class CfgFeature {
531
550
  * Calling this will cause a validation call to the server.
532
551
  */
533
552
  this.selectOption = (option, on) => __awaiter(this, void 0, void 0, function* () {
534
- return yield this._internal.selectOption(option._internal, on, ProductConfigurationBubbleMode.ValidateAndBubbleSelected);
553
+ return yield this._internal.selectOption(option._internal, on, ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups);
535
554
  });
536
555
  this.isSelected = (option) => this._internal.isSelected(option._internal);
537
556
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);