@useinsider/guido 3.17.0-beta.668ca65 → 3.17.0-beta.6cedc6c

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 (23) hide show
  1. package/README.md +5 -5
  2. package/dist/components/Guido.vue.js +7 -7
  3. package/dist/components/Guido.vue2.js +59 -57
  4. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +5 -5
  5. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +31 -30
  6. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue.js +17 -0
  7. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +52 -0
  8. package/dist/components/organisms/extensions/recommendation/StrategyInfoBoxContent.vue2.js +36 -35
  9. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +198 -135
  10. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +1 -1
  11. package/dist/extensions/Blocks/Recommendation/services/configService.js +34 -16
  12. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +47 -37
  13. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +22 -0
  14. package/dist/guido.css +1 -1
  15. package/dist/services/recommendationApi.js +1 -1
  16. package/dist/src/components/organisms/extensions/recommendation/RemoveStrategyModal.vue.d.ts +2 -0
  17. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +38 -8
  18. package/dist/src/extensions/Blocks/Recommendation/services/configService.d.ts +12 -1
  19. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +26 -0
  20. package/dist/src/extensions/Blocks/Recommendation/utils/nodeConfigWriter.d.ts +31 -0
  21. package/dist/src/utils/genericUtil.d.ts +5 -0
  22. package/dist/utils/genericUtil.js +28 -27
  23. package/package.json +2 -2
@@ -1,59 +1,70 @@
1
- var C = Object.defineProperty;
2
- var g = (d, c, t) => c in d ? C(d, c, { enumerable: !0, configurable: !0, writable: !0, value: t }) : d[c] = t;
3
- var n = (d, c, t) => g(d, typeof c != "symbol" ? c + "" : c, t);
1
+ var m = Object.defineProperty;
2
+ var C = (h, l, t) => l in h ? m(h, l, { enumerable: !0, configurable: !0, writable: !0, value: t }) : h[l] = t;
3
+ var r = (h, l, t) => C(h, typeof l != "symbol" ? l + "" : l, t);
4
4
  import { useConfig as p } from "../../../../../composables/useConfig.js";
5
- import { CommonControl as y } from "../../../common-control.js";
5
+ import { ModificationDescription as y } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
6
+ import { CommonControl as N } from "../../../common-control.js";
6
7
  import { CURRENT_CONFIG_VERSION as _, DEFAULT_NODE_CONFIG as a } from "../../constants/defaultConfig.js";
7
- import { RecommendationConfigService as m } from "../../services/configService.js";
8
- import { useRecommendationExtensionStore as N } from "../../store/recommendation.js";
9
- import { AlgorithmControl as I } from "./algorithm.js";
10
- import { ALGORITHM_CONTROL_ID as it } from "./algorithm.js";
11
- import { CurrencyControl as R } from "./currency.js";
12
- import { CURRENCY_CONTROL_ID as at } from "./currency.js";
13
- import { FiltersControl as b } from "./filters.js";
14
- import { FILTERS_CONTROL_ID as lt } from "./filters.js";
15
- import { LayoutOrientationControl as T } from "./layoutOrientation.js";
16
- import { LAYOUT_ORIENTATION_CONTROL_ID as dt } from "./layoutOrientation.js";
17
- import { LocaleControl as S } from "./locale.js";
18
- import { LOCALE_CONTROL_ID as ht } from "./locale.js";
19
- import { PricePlacementControl as O } from "./pricePlacement.js";
20
- import { PRICE_PLACEMENT_CONTROL_ID as Ct } from "./pricePlacement.js";
21
- import { ProductCountControl as P } from "./productCount.js";
22
- import { PRODUCT_COUNT_CONTROL_ID as pt } from "./productCount.js";
23
- import { ProductLayoutControl as L } from "./productLayout.js";
24
- import { PRODUCT_LAYOUT_CONTROL_ID as _t } from "./productLayout.js";
25
- import { ShuffleControl as k } from "./shuffle.js";
26
- import { SHUFFLE_CONTROL_ID as It } from "./shuffle.js";
27
- import { StrategyControl as E } from "./strategy.js";
28
- import { STRATEGY_CONTROL_ID as bt } from "./strategy.js";
29
- import { setCurrencyAttributes as D, getBlockElement as B, isPartnerManagedBlock as w, updateProductContentInPlace as A, regenerateProductRowsWithStyles as v } from "./utils.js";
30
- import { adjustProductsToSize as St, formatProductPrice as Ot, getCardComposition as Pt, getCurrentLayout as Lt, regenerateMobileProductRows as kt, updatePricesInPlace as Et, updateSingleProductContent as Dt } from "./utils.js";
31
- import { useDebounceFn as h } from "../../../../../node_modules/@vueuse/shared/index.js";
32
- const x = "recommendation-id", F = "ui-elements-recommendation-block";
33
- class ot extends y {
8
+ import { RecommendationConfigService as c } from "../../services/configService.js";
9
+ import { useRecommendationExtensionStore as I } from "../../store/recommendation.js";
10
+ import { registerNodeConfigWriter as R } from "../../utils/nodeConfigWriter.js";
11
+ import { AlgorithmControl as b } from "./algorithm.js";
12
+ import { ALGORITHM_CONTROL_ID as ct } from "./algorithm.js";
13
+ import { CurrencyControl as T } from "./currency.js";
14
+ import { CURRENCY_CONTROL_ID as dt } from "./currency.js";
15
+ import { FiltersControl as P } from "./filters.js";
16
+ import { FILTERS_CONTROL_ID as ft } from "./filters.js";
17
+ import { LayoutOrientationControl as S } from "./layoutOrientation.js";
18
+ import { LAYOUT_ORIENTATION_CONTROL_ID as mt } from "./layoutOrientation.js";
19
+ import { LocaleControl as O } from "./locale.js";
20
+ import { LOCALE_CONTROL_ID as pt } from "./locale.js";
21
+ import { PricePlacementControl as L } from "./pricePlacement.js";
22
+ import { PRICE_PLACEMENT_CONTROL_ID as Nt } from "./pricePlacement.js";
23
+ import { ProductCountControl as E } from "./productCount.js";
24
+ import { PRODUCT_COUNT_CONTROL_ID as It } from "./productCount.js";
25
+ import { ProductLayoutControl as k } from "./productLayout.js";
26
+ import { PRODUCT_LAYOUT_CONTROL_ID as bt } from "./productLayout.js";
27
+ import { ShuffleControl as D } from "./shuffle.js";
28
+ import { SHUFFLE_CONTROL_ID as Pt } from "./shuffle.js";
29
+ import { StrategyControl as F } from "./strategy.js";
30
+ import { STRATEGY_CONTROL_ID as Ot } from "./strategy.js";
31
+ import { setCurrencyAttributes as w, getBlockElement as A, isPartnerManagedBlock as v, updateProductContentInPlace as x, regenerateProductRowsWithStyles as B } from "./utils.js";
32
+ import { adjustProductsToSize as Et, formatProductPrice as kt, getCardComposition as Dt, getCurrentLayout as Ft, regenerateMobileProductRows as wt, updatePricesInPlace as At, updateSingleProductContent as vt } from "./utils.js";
33
+ import { useDebounceFn as f } from "../../../../../node_modules/@vueuse/shared/index.js";
34
+ const U = "recommendation-id", $ = "ui-elements-recommendation-block";
35
+ class st extends N {
34
36
  constructor() {
35
37
  super(...arguments);
36
- n(this, "store", N());
37
- n(this, "storeUnsubscription", () => {
38
+ r(this, "store", I());
39
+ r(this, "storeUnsubscription", () => {
38
40
  });
39
- n(this, "syncedBlockIds", /* @__PURE__ */ new Set());
41
+ // blockId -> fingerprint of the node config as it looked at the last sync.
42
+ r(this, "syncedConfigs", /* @__PURE__ */ new Map());
43
+ // A config change waiting to reach the node config. Deferred so it can ride the
44
+ // re-render's single apply() — one user action must cost one undo press.
45
+ r(this, "pendingNodeConfigWrite", null);
46
+ r(this, "pendingConfigFlushTimer", null);
40
47
  // Sub-control instances for lifecycle management
41
- n(this, "algorithmControl", null);
42
- n(this, "localeControl", null);
43
- n(this, "currencyControl", null);
44
- n(this, "productCountControl", null);
45
- n(this, "productLayoutControl", null);
46
- n(this, "filtersControl", null);
47
- n(this, "shuffleControl", null);
48
- n(this, "pricePlacementControl", null);
49
- n(this, "layoutOrientationControl", null);
50
- n(this, "strategyControl", null);
51
- n(this, "rrsEnabled", !1);
48
+ r(this, "algorithmControl", null);
49
+ r(this, "localeControl", null);
50
+ r(this, "currencyControl", null);
51
+ r(this, "productCountControl", null);
52
+ r(this, "productLayoutControl", null);
53
+ r(this, "filtersControl", null);
54
+ r(this, "shuffleControl", null);
55
+ r(this, "pricePlacementControl", null);
56
+ r(this, "layoutOrientationControl", null);
57
+ r(this, "strategyControl", null);
58
+ r(this, "rrsEnabled", !1);
52
59
  /**
53
60
  * Debounced product fetch to prevent rapid API calls during config changes
54
61
  */
55
- n(this, "_debouncedFetchProducts", h(() => {
56
- this.store.fetchRecommendationProducts();
62
+ r(this, "_debouncedFetchProducts", f(async () => {
63
+ try {
64
+ await this.store.fetchRecommendationProducts();
65
+ } finally {
66
+ this._scheduleConfigFlush();
67
+ }
57
68
  }, 500));
58
69
  /**
59
70
  * Debounced content update when products arrive from API.
@@ -66,16 +77,20 @@ class ot extends y {
66
77
  * count. The store pads products to the configured size, so in-place only
67
78
  * fails when the size actually changed.
68
79
  */
69
- n(this, "_debouncedRegenerateWithProducts", h(() => {
80
+ r(this, "_debouncedRegenerateWithProducts", f(() => {
70
81
  const t = this.store.recommendationProducts;
71
- if (!this.currentNode || !this.api || w(this.currentNode))
82
+ if (!this.currentNode || !this.api)
83
+ return;
84
+ const e = this.api.getDocumentModifier(), o = this._stagePendingNodeConfig(e);
85
+ if (v(this.currentNode)) {
86
+ o && (e.apply(new y("Update recommendation configuration")), this.pendingNodeConfigWrite = null, this._clearConfigFlush());
72
87
  return;
73
- const e = this.api.getDocumentModifier();
74
- A({
88
+ }
89
+ x({
75
90
  currentNode: this.currentNode,
76
91
  documentModifier: e,
77
92
  products: t
78
- }) || v({
93
+ }) || B({
79
94
  currentNode: this.currentNode,
80
95
  documentModifier: e,
81
96
  products: t
@@ -83,7 +98,7 @@ class ot extends y {
83
98
  }, 100));
84
99
  }
85
100
  getId() {
86
- return F;
101
+ return $;
87
102
  }
88
103
  get _subControls() {
89
104
  return [
@@ -104,7 +119,7 @@ class ot extends y {
104
119
  return this.rrsEnabled = p().isFeatureEnabled("reusableRecommendationStrategy"), this._subControls.forEach((e) => {
105
120
  var o;
106
121
  return (o = e == null ? void 0 : e.onDestroy) == null ? void 0 : o.call(e);
107
- }), this.algorithmControl = new I(), this.localeControl = new S(), this.currencyControl = new R(), this.productCountControl = new P(), this.productLayoutControl = new L(), this.filtersControl = new b(), this.shuffleControl = new k(), this.pricePlacementControl = new O(), this.layoutOrientationControl = new T(), this.strategyControl = this.rrsEnabled ? new E() : null, this._subControls.forEach((e) => {
122
+ }), this.algorithmControl = new b(), this.localeControl = new O(), this.currencyControl = new T(), this.productCountControl = new E(), this.productLayoutControl = new k(), this.filtersControl = new P(), this.shuffleControl = new D(), this.pricePlacementControl = new L(), this.layoutOrientationControl = new S(), this.strategyControl = this.rrsEnabled ? new F() : null, this._subControls.forEach((e) => {
108
123
  e && (e.api = this.api);
109
124
  }), `
110
125
  <div class="recommendation-controls-container">
@@ -126,13 +141,14 @@ class ot extends y {
126
141
  const e = (i = this.getContainer()) == null ? void 0 : i.querySelector(".recommendation-controls-container");
127
142
  if (!e)
128
143
  return;
129
- const o = this._getRecommendationIdFromNode(t) === null ? null : m.getConfig(t), r = this.rrsEnabled && !!o && (!!(o != null && o.strategyId) || ((o == null ? void 0 : o.configVersion) ?? 0) >= _);
130
- e.classList.toggle("strategy-block", r);
144
+ const o = this._getRecommendationIdFromNode(t) === null ? null : c.getConfig(t), n = this.rrsEnabled && !!o && (!!(o != null && o.strategyId) || ((o == null ? void 0 : o.configVersion) ?? 0) >= _);
145
+ e.classList.toggle("strategy-block", n);
131
146
  }
132
147
  async onRender() {
133
148
  var o;
149
+ R(this.api);
134
150
  const t = this._getRecommendationIdFromNode(this.currentNode), e = t ?? this.store.currentRecommendationId;
135
- if (e !== null && this.store.setCurrentBlock(e), t !== null && !this.syncedBlockIds.has(t) && (this.syncedBlockIds.add(t), this._syncNodeConfigToStore()), this._listenStateUpdates(), this._showControlsForBlock(this.currentNode), e !== null && ((o = this.store.blockStates[e]) != null && o.isInitialized)) {
151
+ if (e !== null && this.store.setCurrentBlock(e), t !== null && this._syncNodeConfigIfChanged(t), this._listenStateUpdates(), this._showControlsForBlock(this.currentNode), e !== null && ((o = this.store.blockStates[e]) != null && o.isInitialized)) {
136
152
  this._initializeSubControls();
137
153
  return;
138
154
  }
@@ -142,15 +158,15 @@ class ot extends y {
142
158
  var o;
143
159
  super.onTemplateNodeUpdated(t);
144
160
  const e = this._getRecommendationIdFromNode(t);
145
- e !== null && e !== this.store.currentRecommendationId && this.store.setCurrentBlock(e), e !== null && !this.syncedBlockIds.has(e) && (this.syncedBlockIds.add(e), this._syncNodeConfigToStore()), this._showControlsForBlock(t), e !== null && !((o = this.store.blockStates[e]) != null && o.isInitialized) && this._fetchBlockData(e).then(() => {
161
+ e !== null && e !== this.store.currentRecommendationId && this.store.setCurrentBlock(e), e !== null && this._syncNodeConfigIfChanged(e), this._showControlsForBlock(t), e !== null && !((o = this.store.blockStates[e]) != null && o.isInitialized) && this._fetchBlockData(e).then(() => {
146
162
  this._initializeSubControls();
147
- }), this._subControls.forEach((r) => {
163
+ }), this._subControls.forEach((n) => {
148
164
  var i;
149
- r != null && r.api && (r.currentNode = t, (i = r.onTemplateNodeUpdated) == null || i.call(r, t));
165
+ n != null && n.api && (n.currentNode = t, (i = n.onTemplateNodeUpdated) == null || i.call(n, t));
150
166
  });
151
167
  }
152
168
  onDestroy() {
153
- this.storeUnsubscription(), this._subControls.forEach((t) => {
169
+ this.storeUnsubscription(), this._flushPendingNodeConfig(), this._subControls.forEach((t) => {
154
170
  var e;
155
171
  return (e = t == null ? void 0 : t.onDestroy) == null ? void 0 : e.call(t);
156
172
  });
@@ -175,9 +191,73 @@ class ot extends y {
175
191
  * Uses triggerRefetch: false because the fetch hasn't happened yet —
176
192
  * values are being prepared for the upcoming initial fetch.
177
193
  */
194
+ /**
195
+ * Re-syncs the store when the node config moved since we last read it.
196
+ *
197
+ * Undo/redo, the code editor and version-history restore rewrite the node
198
+ * config behind the store's back — Stripo exposes no undo signal, so a
199
+ * fingerprint diff on this hook is the only way to notice (SD-149105).
200
+ * An unchanged fingerprint means the move came from us, so runtime store
201
+ * edits are never clobbered (SD-141049).
202
+ */
203
+ _syncNodeConfigIfChanged(t) {
204
+ this.pendingNodeConfigWrite && this.pendingNodeConfigWrite.blockId !== t && this._flushPendingNodeConfig();
205
+ const e = JSON.stringify(c.getConfig(this.currentNode)), o = this.syncedConfigs.get(t);
206
+ this.syncedConfigs.set(t, e), o !== e && (this.pendingNodeConfigWrite || this._syncNodeConfigToStore());
207
+ }
208
+ _capturePendingNodeConfigWrite() {
209
+ const t = this.store.currentRecommendationId;
210
+ if (!this.currentNode || !this.api || t === null)
211
+ return;
212
+ const { filters: e, strategyId: o, size: n } = this.store.recommendationConfigs;
213
+ this.pendingNodeConfigWrite = {
214
+ blockId: t,
215
+ node: this.currentNode,
216
+ updates: { filters: e, strategyId: o, size: n }
217
+ };
218
+ }
219
+ /**
220
+ * Accumulates the pending write into `documentModifier` without applying, so the
221
+ * caller's apply() commits config and re-render as ONE undo entry.
222
+ *
223
+ * Deliberately does not consume the pending record: the product helpers call
224
+ * apply() only when they actually changed something, so this batch may never be
225
+ * committed and the flush has to stay able to write it standalone.
226
+ * @param documentModifier - The batch to accumulate into
227
+ * @returns Whether anything was staged
228
+ */
229
+ _stagePendingNodeConfig(t) {
230
+ const e = this.pendingNodeConfigWrite;
231
+ return !e || !this.currentNode ? !1 : (c.stageConfig(t, this.currentNode, e.updates), !0);
232
+ }
233
+ /**
234
+ * Arms the guaranteed fallback write, once the product fetch has settled. The
235
+ * delay only has to outlast the re-render debounce that fetch triggers — firing
236
+ * earlier writes the config on its own and the undo entry splits in two again.
237
+ */
238
+ _scheduleConfigFlush() {
239
+ this._clearConfigFlush(), this.pendingConfigFlushTimer = setTimeout(() => {
240
+ this.pendingConfigFlushTimer = null, this._flushPendingNodeConfig();
241
+ }, 800);
242
+ }
243
+ _clearConfigFlush() {
244
+ this.pendingConfigFlushTimer !== null && (clearTimeout(this.pendingConfigFlushTimer), this.pendingConfigFlushTimer = null);
245
+ }
246
+ /**
247
+ * Writes the deferred change standalone when the batch it was staged into never
248
+ * committed. Always targets the LIVE node — the captured one is an immutable
249
+ * snapshot the intervening re-render staled, and saveConfig swallows that failure.
250
+ */
251
+ _flushPendingNodeConfig() {
252
+ const t = this.pendingNodeConfigWrite;
253
+ if (this.pendingNodeConfigWrite = null, this._clearConfigFlush(), !t || !this.api)
254
+ return;
255
+ const e = t.blockId === this.store.currentRecommendationId && this.currentNode ? this.currentNode : t.node, o = c.getConfig(e);
256
+ JSON.stringify(o.filters) === JSON.stringify(t.updates.filters) && o.strategyId === t.updates.strategyId && o.size === t.updates.size || c.updateConfig(this.api, e, t.updates, "Update recommendation filters");
257
+ }
178
258
  _syncNodeConfigToStore() {
179
- var r;
180
- const t = m.getConfig(this.currentNode), e = this.store.currentRecommendationId, o = e !== null && ((r = this.store.blockStates[e]) == null ? void 0 : r.isInitialized);
259
+ var n;
260
+ const t = c.getConfig(this.currentNode), e = this.store.currentRecommendationId, o = e !== null && ((n = this.store.blockStates[e]) == null ? void 0 : n.isInitialized);
181
261
  this.store.patchCurrentBlockConfig({
182
262
  strategy: t.strategy,
183
263
  strategyId: t.strategyId,
@@ -219,8 +299,8 @@ class ot extends y {
219
299
  t !== null && this.store.markBlockInitialized(t), (await Promise.allSettled([
220
300
  this.store.fetchRecommendationCreateData(),
221
301
  this.store.fetchRecommendationFilters()
222
- ])).forEach((o, r) => {
223
- o.status === "rejected" && console.warn(`Recommendation block: ${["fetchRecommendationCreateData", "fetchRecommendationFilters"][r]} failed`, o.reason);
302
+ ])).forEach((o, n) => {
303
+ o.status === "rejected" && console.warn(`Recommendation block: ${["fetchRecommendationCreateData", "fetchRecommendationFilters"][n]} failed`, o.reason);
224
304
  }), this._applySmartDefaults();
225
305
  try {
226
306
  await this.store.fetchRecommendationProducts();
@@ -241,12 +321,12 @@ class ot extends y {
241
321
  _applySmartDefaults() {
242
322
  if (!this.currentNode || !this.api)
243
323
  return;
244
- const t = m.getConfig(this.currentNode), e = {};
245
- let o = null, r = null, i = null;
324
+ const t = c.getConfig(this.currentNode), e = {};
325
+ let o = null, n = null, i = null;
246
326
  if (t.currency.code === a.currency.code) {
247
327
  const { currencyList: s } = this.store;
248
328
  s.length > 0 && (s.some(
249
- (u) => u.value === `price.${a.currency.code}`
329
+ (d) => d.value === `price.${a.currency.code}`
250
330
  ) || (o = s[0].value.replace("price.", ""), e.currency = {
251
331
  ...a.currency,
252
332
  code: o,
@@ -256,21 +336,21 @@ class ot extends y {
256
336
  if (t.strategy === a.strategy) {
257
337
  const s = this.store.getActivePredictiveAlgorithms;
258
338
  s.length > 0 && (s.some(
259
- (u) => u.value === a.strategy
260
- ) || (r = s[0].value, e.strategy = r));
339
+ (d) => d.value === a.strategy
340
+ ) || (n = s[0].value, e.strategy = n));
261
341
  }
262
342
  if (t.language === a.language) {
263
343
  const s = this.store.getLanguages;
264
344
  s.length > 0 && (s.some(
265
- (u) => u.value === a.language
345
+ (d) => d.value === a.language
266
346
  ) || (i = s[0].value, e.language = i));
267
347
  }
268
- !o && !r && !i || (m.updateConfig(
348
+ !o && !n && !i || (c.updateConfig(
269
349
  this.api,
270
350
  this.currentNode,
271
351
  e,
272
352
  "Applied smart defaults"
273
- ), o && e.currency && D({
353
+ ), o && e.currency && w({
274
354
  currentNode: this.currentNode,
275
355
  documentModifier: this.api.getDocumentModifier(),
276
356
  currency: e.currency
@@ -286,7 +366,7 @@ class ot extends y {
286
366
  thousandSeparator: a.currency.thousandSeparator
287
367
  }
288
368
  } : {},
289
- ...r ? { strategy: r } : {},
369
+ ...n ? { strategy: n } : {},
290
370
  ...i ? { language: i } : {}
291
371
  }, { triggerRefetch: !1 }));
292
372
  }
@@ -294,14 +374,14 @@ class ot extends y {
294
374
  * Reads the recommendation-id attribute from the block element within the node
295
375
  */
296
376
  _getRecommendationIdFromNode(t) {
297
- const e = B(t);
377
+ const e = A(t);
298
378
  if (!e || !("getAttribute" in e))
299
379
  return null;
300
- const o = e.getAttribute(x);
380
+ const o = e.getAttribute(U);
301
381
  if (!o)
302
382
  return null;
303
- const r = parseInt(o);
304
- return Number.isNaN(r) ? null : r;
383
+ const n = parseInt(o);
384
+ return Number.isNaN(n) ? null : n;
305
385
  }
306
386
  /**
307
387
  * Listen to store changes that require product refresh or regeneration.
@@ -318,70 +398,53 @@ class ot extends y {
318
398
  */
319
399
  _listenStateUpdates() {
320
400
  const { store: t } = this;
321
- let e = t.recommendationProducts, o = t.$state.configVersion, r = t.currentRecommendationId;
401
+ let e = t.recommendationProducts, o = t.$state.configVersion, n = t.currentRecommendationId;
322
402
  this.storeUnsubscription = t.$subscribe(() => {
323
403
  const i = t.currentRecommendationId;
324
- if (i !== r) {
325
- r = i, e = t.recommendationProducts, o = t.$state.configVersion;
404
+ if (i !== n) {
405
+ n = i, e = t.recommendationProducts, o = t.$state.configVersion;
326
406
  return;
327
407
  }
328
408
  const s = t.$state.configVersion;
329
- s !== o && (o = s, this._persistFiltersToNodeConfig(), this._debouncedFetchProducts());
330
- const l = t.recommendationProducts, u = l !== e, f = Array.isArray(l) && l.length > 0;
331
- u && f && (e = l, this._debouncedRegenerateWithProducts());
409
+ s !== o && (o = s, this._capturePendingNodeConfigWrite(), this._debouncedFetchProducts());
410
+ const u = t.recommendationProducts, d = u !== e, g = Array.isArray(u) && u.length > 0;
411
+ d && g && (e = u, this._debouncedRegenerateWithProducts());
332
412
  });
333
413
  }
334
- /**
335
- * Persists filter + strategy state to the Stripo node config so it survives
336
- * save/reload (strategyId must reach esd-ext-config before any save).
337
- * `size` rides along: assigning a strategy writes it to the store only, and a
338
- * stale node config would rebuild the block against the old slot count.
339
- */
340
- _persistFiltersToNodeConfig() {
341
- if (!this.currentNode || !this.api)
342
- return;
343
- const { filters: t, strategyId: e, size: o } = this.store.recommendationConfigs, r = m.getConfig(this.currentNode);
344
- JSON.stringify(r.filters) === JSON.stringify(t) && r.strategyId === e && r.size === o || m.updateConfig(
345
- this.api,
346
- this.currentNode,
347
- { filters: t, strategyId: e, size: o },
348
- "Update recommendation filters"
349
- );
350
- }
351
414
  }
352
415
  export {
353
- it as ALGORITHM_CONTROL_ID,
354
- I as AlgorithmControl,
355
- F as CONTROL_BLOCK_ID,
356
- at as CURRENCY_CONTROL_ID,
357
- R as CurrencyControl,
358
- lt as FILTERS_CONTROL_ID,
359
- b as FiltersControl,
360
- dt as LAYOUT_ORIENTATION_CONTROL_ID,
361
- ht as LOCALE_CONTROL_ID,
362
- T as LayoutOrientationControl,
363
- S as LocaleControl,
364
- Ct as PRICE_PLACEMENT_CONTROL_ID,
365
- pt as PRODUCT_COUNT_CONTROL_ID,
366
- _t as PRODUCT_LAYOUT_CONTROL_ID,
367
- O as PricePlacementControl,
368
- P as ProductCountControl,
369
- L as ProductLayoutControl,
370
- ot as RecommendationBlockControl,
371
- It as SHUFFLE_CONTROL_ID,
372
- bt as STRATEGY_CONTROL_ID,
373
- k as ShuffleControl,
374
- E as StrategyControl,
375
- St as adjustProductsToSize,
376
- Ot as formatProductPrice,
377
- B as getBlockElement,
378
- Pt as getCardComposition,
379
- Lt as getCurrentLayout,
380
- w as isPartnerManagedBlock,
381
- kt as regenerateMobileProductRows,
382
- v as regenerateProductRowsWithStyles,
383
- D as setCurrencyAttributes,
384
- Et as updatePricesInPlace,
385
- A as updateProductContentInPlace,
386
- Dt as updateSingleProductContent
416
+ ct as ALGORITHM_CONTROL_ID,
417
+ b as AlgorithmControl,
418
+ $ as CONTROL_BLOCK_ID,
419
+ dt as CURRENCY_CONTROL_ID,
420
+ T as CurrencyControl,
421
+ ft as FILTERS_CONTROL_ID,
422
+ P as FiltersControl,
423
+ mt as LAYOUT_ORIENTATION_CONTROL_ID,
424
+ pt as LOCALE_CONTROL_ID,
425
+ S as LayoutOrientationControl,
426
+ O as LocaleControl,
427
+ Nt as PRICE_PLACEMENT_CONTROL_ID,
428
+ It as PRODUCT_COUNT_CONTROL_ID,
429
+ bt as PRODUCT_LAYOUT_CONTROL_ID,
430
+ L as PricePlacementControl,
431
+ E as ProductCountControl,
432
+ k as ProductLayoutControl,
433
+ st as RecommendationBlockControl,
434
+ Pt as SHUFFLE_CONTROL_ID,
435
+ Ot as STRATEGY_CONTROL_ID,
436
+ D as ShuffleControl,
437
+ F as StrategyControl,
438
+ Et as adjustProductsToSize,
439
+ kt as formatProductPrice,
440
+ A as getBlockElement,
441
+ Dt as getCardComposition,
442
+ Ft as getCurrentLayout,
443
+ v as isPartnerManagedBlock,
444
+ wt as regenerateMobileProductRows,
445
+ B as regenerateProductRowsWithStyles,
446
+ w as setCurrencyAttributes,
447
+ At as updatePricesInPlace,
448
+ x as updateProductContentInPlace,
449
+ vt as updateSingleProductContent
387
450
  };
@@ -104,7 +104,7 @@ class f extends h {
104
104
  const i = t.querySelector(a);
105
105
  i && (i.addEventListener("click", n), this.listeners.push({ element: i, handler: n }));
106
106
  };
107
- e("#guido__btn-assign-strategy", () => this.store.openAssignStrategyDrawer()), e("#guido__btn-change-strategy", () => this.store.openAssignStrategyDrawer()), e("#guido__btn-remove-strategy", () => this.store.removeStrategy());
107
+ e("#guido__btn-assign-strategy", () => this.store.openAssignStrategyDrawer()), e("#guido__btn-change-strategy", () => this.store.openAssignStrategyDrawer()), e("#guido__btn-remove-strategy", () => this.store.openRemoveStrategyModal());
108
108
  }
109
109
  _renderState() {
110
110
  var l;
@@ -2,13 +2,13 @@ import { ModificationDescription as h } from "../../../../node_modules/@stripoin
2
2
  import { PRE_RRS_CONFIG_VERSION as C, DEFAULT_NODE_CONFIG as c } from "../constants/defaultConfig.js";
3
3
  import { setCurrencyAttributes as D } from "../controls/main/utils.js";
4
4
  import { hasMinimalConfig as S } from "../types/nodeConfig.js";
5
- function A(n) {
6
- return n === "." || n === "," || n === " ";
5
+ function A(r) {
6
+ return r === "." || r === "," || r === " ";
7
7
  }
8
- function N(n) {
9
- return n === "." || n === "," || n === " " || n === "";
8
+ function N(r) {
9
+ return r === "." || r === "," || r === " " || r === "";
10
10
  }
11
- class F {
11
+ class M {
12
12
  // ========================================================================
13
13
  // Read Operations
14
14
  // ========================================================================
@@ -83,9 +83,27 @@ class F {
83
83
  * @returns The new complete configuration
84
84
  */
85
85
  static updateConfig(i, t, o, e) {
86
- const r = this.getConfig(t), u = this.deepMerge(r, o);
86
+ const n = this.getConfig(t), u = this.deepMerge(n, o);
87
87
  return this.saveConfig(i, t, u, e), u;
88
88
  }
89
+ /**
90
+ * Accumulate a config update into an existing modifier batch, WITHOUT applying.
91
+ *
92
+ * The caller owns the apply(), so the write shares one undo entry with the rest
93
+ * of that batch — a change whose visible result only lands after an async product
94
+ * fetch would otherwise cost the user two undo presses (SD-149105).
95
+ * @param documentModifier - The batch to accumulate into (caller applies)
96
+ * @param node - The immutable HTML node to write to
97
+ * @param updates - Partial config with values to update
98
+ */
99
+ static stageConfig(i, t, o) {
100
+ const e = this.deepMerge(this.getConfig(t), o);
101
+ try {
102
+ i.modifyHtml(t).setNodeConfig(e);
103
+ } catch (n) {
104
+ console.warn("[RecommendationConfigService] Failed to stage config:", n);
105
+ }
106
+ }
89
107
  /**
90
108
  * Initialize configuration for a new block
91
109
  *
@@ -126,8 +144,8 @@ class F {
126
144
  static saveConfig(i, t, o, e) {
127
145
  try {
128
146
  i.getDocumentModifier().modifyHtml(t).setNodeConfig(o).apply(new h(e));
129
- } catch (r) {
130
- console.warn("[RecommendationConfigService] Failed to save config:", r);
147
+ } catch (n) {
148
+ console.warn("[RecommendationConfigService] Failed to save config:", n);
131
149
  }
132
150
  }
133
151
  // ========================================================================
@@ -164,20 +182,20 @@ class F {
164
182
  s && typeof s == "object" && Object.assign(o, s);
165
183
  } catch {
166
184
  }
167
- const r = t.getAttribute("data-layout");
168
- r === "list" || r === "horizontal" ? o.layout = "list" : (r === "grid" || r === "vertical") && (o.layout = "grid");
185
+ const n = t.getAttribute("data-layout");
186
+ n === "list" || n === "horizontal" ? o.layout = "list" : (n === "grid" || n === "vertical") && (o.layout = "grid");
169
187
  const u = t.getAttribute("data-card-composition");
170
188
  u && (o.composition = u.split(",").filter(Boolean));
171
189
  const l = t.getAttribute("data-column-spacing");
172
190
  l && (o.columnSpacing = parseInt(l) || c.columnSpacing);
173
- const b = t.getAttribute("data-row-spacing");
174
- if (b && (o.rowSpacing = parseInt(b) || c.rowSpacing), !o.currency) {
175
- const s = t.getAttribute("currency"), p = t.getAttribute("currency-symbol"), y = t.getAttribute("currency-alignment"), f = t.getAttribute("currency-thousand-separator"), g = t.getAttribute("currency-decimal-separator"), m = t.getAttribute("currency-decimal-count");
176
- if (s || p || y || f || g || m) {
191
+ const p = t.getAttribute("data-row-spacing");
192
+ if (p && (o.rowSpacing = parseInt(p) || c.rowSpacing), !o.currency) {
193
+ const s = t.getAttribute("currency"), b = t.getAttribute("currency-symbol"), y = t.getAttribute("currency-alignment"), f = t.getAttribute("currency-thousand-separator"), g = t.getAttribute("currency-decimal-separator"), m = t.getAttribute("currency-decimal-count");
194
+ if (s || b || y || f || g || m) {
177
195
  const a = c.currency, d = m ? parseInt(m) : NaN;
178
196
  o.currency = {
179
197
  code: s ?? a.code,
180
- symbol: p ?? a.symbol,
198
+ symbol: b ?? a.symbol,
181
199
  alignment: y === "0" ? "before" : "after",
182
200
  decimalCount: Number.isFinite(d) ? d : a.decimalCount,
183
201
  decimalSeparator: A(g) ? g : a.decimalSeparator,
@@ -279,5 +297,5 @@ class F {
279
297
  }
280
298
  }
281
299
  export {
282
- F as RecommendationConfigService
300
+ M as RecommendationConfigService
283
301
  };