@useinsider/guido 3.20.0 → 3.20.1-beta.8a05319

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 (26) hide show
  1. package/dist/config/compiler/itemsCompilerRules.js +14 -5
  2. package/dist/config/compiler/utils/itemsCompilerUtils.js +48 -21
  3. package/dist/config/migrator/itemsBlockMigrator.js +112 -108
  4. package/dist/extensions/Blocks/Items/block.js +78 -67
  5. package/dist/extensions/Blocks/Items/constants/cardWidth.js +20 -0
  6. package/dist/extensions/Blocks/Items/layouts/vertical.html.js +1 -1
  7. package/dist/extensions/Blocks/Recommendation/block.js +51 -44
  8. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +90 -73
  9. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +209 -203
  10. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +122 -90
  11. package/dist/extensions/Blocks/Recommendation/utils/blockUrlConfig.js +25 -0
  12. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +46 -25
  13. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +40 -17
  14. package/dist/src/config/compiler/utils/itemsCompilerUtils.d.ts +5 -0
  15. package/dist/src/extensions/Blocks/Items/block.d.ts +5 -0
  16. package/dist/src/extensions/Blocks/Items/constants/cardWidth.d.ts +12 -0
  17. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +2 -0
  18. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +2 -0
  19. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +18 -5
  20. package/dist/src/extensions/Blocks/Recommendation/utils/blockUrlConfig.d.ts +4 -0
  21. package/dist/src/extensions/Blocks/Recommendation/utils/nodeConfigWriter.d.ts +3 -1
  22. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +7 -0
  23. package/dist/src/utils/liquidProtection.d.ts +3 -0
  24. package/dist/utils/liquidProtection.js +5 -0
  25. package/dist/utils/templatePreparation.js +61 -85
  26. package/package.json +1 -1
@@ -1,27 +1,31 @@
1
- import { useToaster as _ } from "../../../../composables/useToaster.js";
2
- import { useTranslations as L } from "../../../../composables/useTranslations.js";
3
- import { getRecommendationFeedSourceMaps as F, strategyEditPath as U, strategyCreatePath as N, getOperatorOptions as M, PriceAttributes as E } from "../../../../enums/extensions/recommendationBlock.js";
4
- import { ToasterTypeOptions as B } from "../../../../enums/toaster.js";
5
- import { useRecommendationApi as z } from "../../../../services/recommendationApi.js";
1
+ import { useToaster as U } from "../../../../composables/useToaster.js";
2
+ import { useTranslations as M } from "../../../../composables/useTranslations.js";
3
+ import { getRecommendationFeedSourceMaps as w, strategyEditPath as N, strategyCreatePath as B, getOperatorOptions as z, PriceAttributes as E } from "../../../../enums/extensions/recommendationBlock.js";
4
+ import { ToasterTypeOptions as O } from "../../../../enums/toaster.js";
5
+ import { useRecommendationApi as A } from "../../../../services/recommendationApi.js";
6
6
  import { useConfigStore as R } from "../../../../stores/config.js";
7
- import { defineStore as A } from "pinia";
7
+ import { defineStore as x } from "pinia";
8
8
  import "../constants/selectors.js";
9
- import { DEFAULT_MOBILE_CARDS_IN_ROW as O, DEFAULT_CARDS_IN_ROW as x } from "../constants/layout.js";
10
- import { CURRENT_CONFIG_VERSION as C, DEFAULT_NODE_CONFIG as T, EXCLUDED_ALGORITHM_IDS as V } from "../constants/defaultConfig.js";
11
- import { getDefaultProducts as w } from "../templates/utils.js";
12
- import { generateCompleteFilterQuery as P } from "../utils/filterUtil.js";
13
- import { isLegacyStrategyConfig as j } from "../utils/legacyStrategyMigration.js";
14
- import { getPartnerRecommendationParams as $ } from "../utils/partnerCustomizations.js";
15
- import { sanitizeProductIds as q } from "../utils/productIds.js";
16
- import { isFilterValid as K } from "../validation/filterSchema.js";
17
- import { getInvalidFields as H, isConfigValid as J } from "../validation/requiredFields.js";
18
- const m = z();
19
- let S = null, p = null, y = null, I = "", k = null, b = 0;
20
- const g = /* @__PURE__ */ new Map();
9
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as T, DEFAULT_CARDS_IN_ROW as V } from "../constants/layout.js";
10
+ import { CURRENT_CONFIG_VERSION as C, DEFAULT_NODE_CONFIG as j, EXCLUDED_ALGORITHM_IDS as $ } from "../constants/defaultConfig.js";
11
+ import { getDefaultProducts as P } from "../templates/utils.js";
12
+ import { generateCompleteFilterQuery as G } from "../utils/filterUtil.js";
13
+ import { isLegacyStrategyConfig as q } from "../utils/legacyStrategyMigration.js";
14
+ import { getPartnerRecommendationParams as K } from "../utils/partnerCustomizations.js";
15
+ import { sanitizeProductIds as H } from "../utils/productIds.js";
16
+ import { isFilterValid as J } from "../validation/filterSchema.js";
17
+ import { getInvalidFields as Q, isConfigValid as W } from "../validation/requiredFields.js";
18
+ const u = A();
19
+ let S = null, y = null, p = null, I = "", k = null, b = 0;
20
+ const m = /* @__PURE__ */ new Map(), D = /* @__PURE__ */ new Map();
21
+ function X(t) {
22
+ const e = D.get(t);
23
+ return e && e.token === m.get(t) ? e.outcome : !1;
24
+ }
21
25
  let l = null;
22
- function D() {
26
+ function L() {
23
27
  return {
24
- cardsInRow: x,
28
+ cardsInRow: V,
25
29
  currencySettings: {
26
30
  name: "USD",
27
31
  value: "USD",
@@ -35,7 +39,7 @@ function D() {
35
39
  productIds: [],
36
40
  id: 1,
37
41
  language: "en_US",
38
- mobileCardsInRow: O,
42
+ mobileCardsInRow: T,
39
43
  mobileLayoutEnabled: !1,
40
44
  orientation: "grid",
41
45
  recommendedProducts: [],
@@ -56,9 +60,9 @@ function D() {
56
60
  priceHideIfSameAsDiscounted: !1
57
61
  };
58
62
  }
59
- function G() {
63
+ function _() {
60
64
  return {
61
- recommendationConfigs: D(),
65
+ recommendationConfigs: L(),
62
66
  recommendationProducts: [],
63
67
  filterStatus: !1,
64
68
  filterSelectionDrawerStatus: !1,
@@ -72,7 +76,7 @@ function G() {
72
76
  legacyConversionLoading: !1
73
77
  };
74
78
  }
75
- const Q = () => ({
79
+ const Y = () => ({
76
80
  recommendationCampaignUrls: {},
77
81
  activePredictiveAlgorithms: [],
78
82
  languages: {},
@@ -87,8 +91,8 @@ const Q = () => ({
87
91
  currentRecommendationId: null,
88
92
  strategyPanelOpen: !1,
89
93
  configVersion: 0
90
- }), ft = A("guidoRecommendationExtension", {
91
- state: () => Q(),
94
+ }), St = x("guidoRecommendationExtension", {
95
+ state: () => Y(),
92
96
  getters: {
93
97
  // ====================================================================
94
98
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -98,7 +102,7 @@ const Q = () => ({
98
102
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
99
103
  */
100
104
  recommendationConfigs(t) {
101
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : D();
105
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : L();
102
106
  },
103
107
  /**
104
108
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -119,7 +123,7 @@ const Q = () => ({
119
123
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].removeStrategyModalStatus : !1;
120
124
  },
121
125
  isLegacyStrategyBlock() {
122
- return R().isFeatureEnabled("reusableRecommendationStrategy") && j(this.recommendationConfigs);
126
+ return R().isFeatureEnabled("reusableRecommendationStrategy") && q(this.recommendationConfigs);
123
127
  },
124
128
  legacyConversionLoading(t) {
125
129
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyConversionLoading ?? !1 : !1;
@@ -162,8 +166,8 @@ const Q = () => ({
162
166
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
163
167
  },
164
168
  getActivePredictiveAlgorithms: (t) => {
165
- const e = F(), r = [];
166
- return t.activePredictiveAlgorithms.filter((n) => !V.includes(n)).forEach((n) => {
169
+ const e = w(), r = [];
170
+ return t.activePredictiveAlgorithms.filter((n) => !$.includes(n)).forEach((n) => {
167
171
  r.push(...e.filter((s) => s.id === n));
168
172
  }), r.map((n) => ({
169
173
  text: n.name,
@@ -203,7 +207,7 @@ const Q = () => ({
203
207
  setCurrentBlock(t) {
204
208
  this.blockStates[t] || (this.blockStates = {
205
209
  ...this.blockStates,
206
- [t]: G()
210
+ [t]: _()
207
211
  }), this.currentRecommendationId = t;
208
212
  },
209
213
  /**
@@ -275,7 +279,7 @@ const Q = () => ({
275
279
  decimalCount: String(e.currencyDecimalCount),
276
280
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
277
281
  thousandSeparator: r(e.currencyThousandSeparator, ".")
278
- }, s = !this.blockStates[t], i = s ? G() : this.blockStates[t];
282
+ }, s = !this.blockStates[t], i = s ? _() : this.blockStates[t];
279
283
  i.recommendationConfigs = {
280
284
  ...i.recommendationConfigs,
281
285
  strategy: e.strategy,
@@ -314,7 +318,7 @@ const Q = () => ({
314
318
  * Creates a filter with the first available attribute and operator pre-selected.
315
319
  */
316
320
  createDefaultFilter(t, e) {
317
- const [r] = this.getFilterList, [n] = M(r == null ? void 0 : r.type);
321
+ const [r] = this.getFilterList, [n] = z(r == null ? void 0 : r.type);
318
322
  return {
319
323
  type: "standardFilter",
320
324
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -390,7 +394,7 @@ const Q = () => ({
390
394
  const t = b, e = (async () => {
391
395
  this.strategiesLoading = !0;
392
396
  try {
393
- const r = await m.fetchRecommendationStrategies();
397
+ const r = await u.fetchRecommendationStrategies();
394
398
  if (t !== b)
395
399
  return;
396
400
  this.strategies = r, this.strategiesFetched = !0;
@@ -434,9 +438,9 @@ const Q = () => ({
434
438
  _armStrategyPageReturn(t) {
435
439
  const e = window.open(t, "_blank");
436
440
  if (!e)
437
- return _().showToaster({
438
- type: B.Error,
439
- message: L()("discovery-strategy.error-description")
441
+ return U().showToaster({
442
+ type: O.Error,
443
+ message: M()("discovery-strategy.error-description")
440
444
  }), !1;
441
445
  e.opener = null, l == null || l();
442
446
  let r = !1;
@@ -471,20 +475,20 @@ const Q = () => ({
471
475
  * new strategy is offered; the user picks it themselves.
472
476
  */
473
477
  openStrategyCreation() {
474
- return this._armStrategyPageReturn(N());
478
+ return this._armStrategyPageReturn(B());
475
479
  },
476
480
  /**
477
481
  * Opens the strategy's edit page. Every return refreshes the list so the block reflects the
478
482
  * edited configuration.
479
483
  */
480
484
  openStrategyEdit(t) {
481
- return this._armStrategyPageReturn(U(t));
485
+ return this._armStrategyPageReturn(N(t));
482
486
  },
483
487
  async resolveStrategyUrlTemplate(t, e) {
484
488
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
485
489
  if (s)
486
490
  return s;
487
- const { full_path: i = "" } = await m.generateStrategyEndpoint(t, e) ?? {};
491
+ const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
488
492
  return n && i && (this.strategyUrlTemplates = {
489
493
  ...this.strategyUrlTemplates,
490
494
  [n]: i
@@ -502,7 +506,7 @@ const Q = () => ({
502
506
  throw new Error("[Guido] convertLegacyStrategy called without an active block");
503
507
  this.blockStates[r].legacyConversionLoading = !0;
504
508
  try {
505
- const n = await m.migrateLegacyStrategy(t, e);
509
+ const n = await u.migrateLegacyStrategy(t, e);
506
510
  this.invalidateStrategies();
507
511
  try {
508
512
  await this.fetchStrategies();
@@ -533,7 +537,7 @@ const Q = () => ({
533
537
  this.patchCurrentBlockConfig({
534
538
  strategyId: "",
535
539
  strategy: "",
536
- size: T.size,
540
+ size: j.size,
537
541
  nodeConfigVersion: C
538
542
  });
539
543
  },
@@ -551,7 +555,7 @@ const Q = () => ({
551
555
  activePredictiveAlgorithms: t,
552
556
  languages: e,
553
557
  currencies: r
554
- } = await m.fetchRecommendationCreateData();
558
+ } = await u.fetchRecommendationCreateData();
555
559
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
556
560
  const n = this.blockStates[this.currentRecommendationId];
557
561
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -567,18 +571,18 @@ const Q = () => ({
567
571
  },
568
572
  async fetchRecommendationFilters() {
569
573
  if (!Object.keys(this.filterList).length) {
570
- if (p) {
571
- await p;
574
+ if (y) {
575
+ await y;
572
576
  return;
573
577
  }
574
- p = (async () => {
575
- const t = await m.fetchRecommendationFilters();
578
+ y = (async () => {
579
+ const t = await u.fetchRecommendationFilters();
576
580
  this.filterList = t;
577
581
  })();
578
582
  try {
579
- await p;
583
+ await y;
580
584
  } finally {
581
- p = null;
585
+ y = null;
582
586
  }
583
587
  }
584
588
  },
@@ -607,7 +611,7 @@ const Q = () => ({
607
611
  const n = [...e.recommendationConfigs.filters];
608
612
  n[r] = {
609
613
  ...t,
610
- isValid: K(t)
614
+ isValid: J(t)
611
615
  }, e.recommendationConfigs.filters = n;
612
616
  }
613
617
  },
@@ -621,7 +625,7 @@ const Q = () => ({
621
625
  let i = 1;
622
626
  n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
623
627
  } else {
624
- const i = [...new Set(n.map((a) => a.filterGroup))].sort((a, d) => a - d), o = new Map(i.map((a, d) => [a, d + 1]));
628
+ const i = [...new Set(n.map((a) => a.filterGroup))].sort((a, f) => a - f), o = new Map(i.map((a, f) => [a, f + 1]));
625
629
  n = n.map((a) => ({
626
630
  ...a,
627
631
  filterGroup: o.get(a.filterGroup) ?? a.filterGroup
@@ -645,7 +649,7 @@ const Q = () => ({
645
649
  }), e.recommendationConfigs.filters = r;
646
650
  },
647
651
  generateFilterQuery() {
648
- return P(this.recommendationConfigs.filters);
652
+ return G(this.recommendationConfigs.filters);
649
653
  },
650
654
  /**
651
655
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -653,14 +657,14 @@ const Q = () => ({
653
657
  * every block's recommendationConfigs across user edits.
654
658
  */
655
659
  hasInvalidBlock() {
656
- return Object.values(this.blockStates).some((t) => !J(t.recommendationConfigs, this));
660
+ return Object.values(this.blockStates).some((t) => !W(t.recommendationConfigs, this));
657
661
  },
658
662
  /**
659
663
  * Which required fields are missing across the design's blocks, so the save
660
664
  * toaster can name the cause instead of always showing the generic message.
661
665
  */
662
666
  invalidBlockFields() {
663
- return Object.values(this.blockStates).flatMap((t) => H(t.recommendationConfigs, this));
667
+ return Object.values(this.blockStates).flatMap((t) => Q(t.recommendationConfigs, this));
664
668
  },
665
669
  // ====================================================================
666
670
  // Per-Block Product Fetching
@@ -673,15 +677,15 @@ const Q = () => ({
673
677
  this.configVersion,
674
678
  b
675
679
  ].join(":");
676
- if (y && I === t) {
677
- await y;
680
+ if (p && I === t) {
681
+ await p;
678
682
  return;
679
683
  }
680
- I = t, y = this._doFetchProducts();
684
+ I = t, p = this._doFetchProducts();
681
685
  try {
682
- await y;
686
+ await p;
683
687
  } finally {
684
- I === t && (y = null, I = "");
688
+ I === t && (p = null, I = "");
685
689
  }
686
690
  },
687
691
  async _doFetchProducts() {
@@ -691,14 +695,14 @@ const Q = () => ({
691
695
  await this._doFetchStrategyProducts(t);
692
696
  return;
693
697
  }
694
- const s = (g.get(t) ?? 0) + 1;
695
- g.set(t, s);
698
+ const s = (m.get(t) ?? 0) + 1;
699
+ m.set(t, s);
696
700
  const i = parseInt(r.size) || 6;
697
701
  if (!r.strategy) {
698
702
  this._setBlockProducts(t, [], i);
699
703
  return;
700
704
  }
701
- const o = r.filters.filter((f) => f.isValid), a = P(o), d = ((h = F().find((f) => f.key === r.strategy)) == null ? void 0 : h.path) || "", c = {
705
+ const o = r.filters.filter((g) => g.isValid), a = G(o), f = ((h = w().find((g) => g.key === r.strategy)) == null ? void 0 : h.path) || "", c = {
702
706
  locale: r.language,
703
707
  currency: r.currencySettings.value,
704
708
  partnerName: n.partnerName,
@@ -706,58 +710,86 @@ const Q = () => ({
706
710
  details: !0,
707
711
  campaignId: n.variationId
708
712
  };
709
- r.strategy === "manualMerchandising" ? c.productId = q(r.productIds) : r.strategy === "similarViewed" && (c.productId = "{itemId}"), r.strategy === "userBased" && (c.userId = "{user_id}"), a && (c.filter = a), r.shuffleProducts && (c.shuffle = !0), Object.assign(
713
+ r.strategy === "manualMerchandising" ? c.productId = H(r.productIds) : r.strategy === "similarViewed" && (c.productId = "{itemId}"), r.strategy === "userBased" && (c.userId = "{user_id}"), a && (c.filter = a), r.shuffleProducts && (c.shuffle = !0), Object.assign(
710
714
  c,
711
- $(n.partnerName, r.strategy)
715
+ K(n.partnerName, r.strategy)
712
716
  );
713
- let u;
717
+ let d;
714
718
  try {
715
- u = await m.fetchRecommendationProducts(d, c);
719
+ d = await u.fetchRecommendationProducts(f, c);
716
720
  } catch {
717
- u = [];
721
+ d = [];
718
722
  }
719
- g.get(t) === s && this._setBlockProducts(t, u, i);
723
+ m.get(t) === s && this._setBlockProducts(t, d, i);
720
724
  },
721
725
  /**
722
- * Block-scoped counterpart of `fetchRecommendationProducts`, which only ever serves the
723
- * selected block. The legacy conversion must refresh the products of the block that
724
- * started it even after the user selected another one.
726
+ * Block-scoped counterpart of `fetchRecommendationProducts`, which only serves the selected block:
727
+ * the legacy conversion and the duplicate refresh must reach a block that may no longer be selected.
728
+ * @returns whether real products arrived
725
729
  */
726
- async fetchStrategyProductsForBlock(t) {
727
- var e;
728
- (e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId && await this._doFetchStrategyProducts(t);
730
+ async fetchStrategyProductsForBlock(t, e = {}) {
731
+ var r;
732
+ return (r = this.blockStates[t]) != null && r.recommendationConfigs.strategyId ? this._doFetchStrategyProducts(t, e) : !1;
729
733
  },
730
- async _doFetchStrategyProducts(t) {
731
- const e = (g.get(t) ?? 0) + 1;
732
- g.set(t, e);
733
- const { recommendationConfigs: r } = this.blockStates[t];
734
- let n = [], s = parseInt(r.size) || 6;
734
+ /**
735
+ * Reloads a block's products and adopts its strategy's current size, which may have
736
+ * moved since the block was assigned.
737
+ * @returns whether a fresh listing holds the strategy and its products arrived
738
+ */
739
+ async refreshStrategyBlock(t) {
740
+ var s;
741
+ const e = (s = this.blockStates[t]) == null ? void 0 : s.recommendationConfigs.strategyId;
742
+ if (!e)
743
+ return !1;
744
+ try {
745
+ await this.fetchStrategies();
746
+ } catch {
747
+ return !1;
748
+ }
749
+ const r = this.strategiesFetched ? this.strategies.find((i) => String(i.strategy_id) === e) : void 0;
750
+ if (!r || !await this.fetchStrategyProductsForBlock(t, { keepOnFailure: !0 }))
751
+ return !1;
752
+ const n = this.blockStates[t];
753
+ return (n == null ? void 0 : n.recommendationConfigs.strategyId) !== e ? !1 : (r.size !== void 0 && (n.recommendationConfigs = {
754
+ ...n.recommendationConfigs,
755
+ size: String(r.size)
756
+ }), !0);
757
+ },
758
+ _doFetchStrategyProducts(t, e = {}) {
759
+ const r = (m.get(t) ?? 0) + 1;
760
+ m.set(t, r);
761
+ const n = this._loadStrategyProducts(t, r, e);
762
+ return D.set(t, { token: r, outcome: n }), n;
763
+ },
764
+ async _loadStrategyProducts(t, e, { keepOnFailure: r = !1 }) {
765
+ const { recommendationConfigs: n } = this.blockStates[t];
766
+ let s = [], i = parseInt(n.size) || 6;
735
767
  try {
736
768
  await this.fetchStrategies();
737
- const i = this.strategies.find(
738
- (o) => String(o.strategy_id) === r.strategyId
769
+ const o = this.strategies.find(
770
+ (a) => String(a.strategy_id) === n.strategyId
739
771
  );
740
- if (i) {
741
- s = parseInt(String(i.size ?? "")) || s;
742
- const o = r.currencySettings.value, d = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), c = d.indexOf("?"), u = c === -1 ? d : d.slice(0, c), h = (c === -1 ? "" : d.slice(c + 1)).split("&").filter((v) => v && !v.startsWith("user_id=")), f = h.length ? `${u}?${h.join("&")}` : u;
743
- f && (n = await m.fetchRecommendationProductsByUrl(f));
772
+ if (o) {
773
+ i = parseInt(String(o.size ?? "")) || i;
774
+ const a = n.currencySettings.value, c = (await this.resolveStrategyUrlTemplate(o, a)).replaceAll("{locale}", n.language).replaceAll("{currency}", a), d = c.indexOf("?"), h = d === -1 ? c : c.slice(0, d), g = (d === -1 ? "" : c.slice(d + 1)).split("&").filter((F) => F && !F.startsWith("user_id=")), v = g.length ? `${h}?${g.join("&")}` : h;
775
+ v && (s = await u.fetchRecommendationProductsByUrl(v));
744
776
  }
745
777
  } catch {
746
- n = [];
778
+ s = [];
747
779
  }
748
- g.get(t) === e && this._setBlockProducts(t, n, s);
780
+ return m.get(t) !== e ? X(t) : !s.length && r ? !1 : (this._setBlockProducts(t, s, i), s.length > 0);
749
781
  },
750
782
  _setBlockProducts(t, e, r) {
751
783
  if (!this.blockStates[t])
752
784
  return;
753
- const n = e.length > 0 ? e : w(r);
785
+ const n = e.length > 0 ? e : P(r);
754
786
  this.blockStates[t].recommendationProducts = [
755
787
  ...n.slice(0, r),
756
- ...w(Math.max(0, r - n.length))
788
+ ...P(Math.max(0, r - n.length))
757
789
  ];
758
790
  }
759
791
  }
760
792
  });
761
793
  export {
762
- ft as useRecommendationExtensionStore
794
+ St as useRecommendationExtensionStore
763
795
  };
@@ -0,0 +1,25 @@
1
+ import { RRS_CONFIG_VERSION as d, DEFAULT_CURRENCY as t, DEFAULT_NODE_CONFIG as e } from "../constants/defaultConfig.js";
2
+ import { mapLegacyStrategy as n } from "./legacyStrategyMap.js";
3
+ function S(r) {
4
+ var a, u, c, s, l, o;
5
+ const y = !!r.strategyId || (r.configVersion ?? 0) >= d;
6
+ return {
7
+ strategy: n(r.strategy) || (y ? "" : e.strategy),
8
+ strategyId: r.strategyId ?? e.strategyId,
9
+ language: r.language ?? e.language,
10
+ size: r.size ?? e.size,
11
+ // Copied: the source can be a live node config or the shared defaults.
12
+ productIds: [...r.productIds ?? e.productIds],
13
+ filters: structuredClone(r.filters ?? e.filters),
14
+ shuffleProducts: r.shuffleProducts ?? e.shuffleProducts,
15
+ currencyCode: ((a = r.currency) == null ? void 0 : a.code) ?? t.code,
16
+ currencySymbol: ((u = r.currency) == null ? void 0 : u.symbol) ?? t.symbol,
17
+ currencyAlignment: ((c = r.currency) == null ? void 0 : c.alignment) ?? t.alignment,
18
+ currencyDecimalCount: ((s = r.currency) == null ? void 0 : s.decimalCount) ?? t.decimalCount,
19
+ currencyDecimalSeparator: ((l = r.currency) == null ? void 0 : l.decimalSeparator) ?? t.decimalSeparator,
20
+ currencyThousandSeparator: ((o = r.currency) == null ? void 0 : o.thousandSeparator) ?? t.thousandSeparator
21
+ };
22
+ }
23
+ export {
24
+ S as toBlockUrlConfig
25
+ };
@@ -1,32 +1,53 @@
1
- import { ModificationDescription as g } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
- import { RECOMMENDATION_ID_ATTR as s } from "../constants/selectors.js";
3
- import { isPartnerManagedBlock as p, updateProductContentInPlace as C, regenerateProductRowsWithStyles as N } from "../controls/main/utils.js";
1
+ import { ModificationDescription as s } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
+ import { RECOMMENDATION_ID_ATTR as a } from "../constants/selectors.js";
3
+ import { isPartnerManagedBlock as C, updateProductContentInPlace as p, regenerateProductRowsWithStyles as y } from "../controls/main/utils.js";
4
4
  import { RecommendationConfigService as m } from "../services/configService.js";
5
- let o = null;
6
- function D(n) {
7
- o = n;
5
+ import { useRecommendationExtensionStore as N } from "../store/recommendation.js";
6
+ let n = null;
7
+ function B(e) {
8
+ n = e;
8
9
  }
9
- function P(n, r, c, l) {
10
- const i = o == null ? void 0 : o.getDocumentRootHtmlNode();
11
- if (!o || !i || !("querySelector" in i))
10
+ function S(e, t) {
11
+ const o = N(), i = o.currentRecommendationId;
12
+ if (i === e || !o.blockStates[e]) {
13
+ t();
14
+ return;
15
+ }
16
+ o.setCurrentBlock(e);
17
+ try {
18
+ t();
19
+ } finally {
20
+ o.$patch({ currentRecommendationId: i });
21
+ }
22
+ }
23
+ function P(e) {
24
+ const t = n == null ? void 0 : n.getDocumentRootHtmlNode();
25
+ return !t || !("querySelectorAll" in t) ? [] : t.querySelectorAll(`[${a}="${e}"]`).map((o) => m.getConfig(o));
26
+ }
27
+ function $(e, t, o, i) {
28
+ const f = n == null ? void 0 : n.getDocumentRootHtmlNode();
29
+ if (!n || !f || !("querySelector" in f))
12
30
  return !1;
13
- const e = i.querySelector(`[${s}="${n}"]`);
14
- if (!e)
31
+ const r = f.querySelector(`[${a}="${e}"]`);
32
+ if (!r)
15
33
  return !1;
16
- const t = o.getDocumentModifier(), a = m.getConfig(e), d = Object.keys(r).some((u) => JSON.stringify(a[u]) !== JSON.stringify(r[u]));
17
- if (m.stageConfig(t, e, r), p(e))
18
- return t.apply(new g(l)), !0;
19
- let f = !1;
20
- return C({
21
- currentNode: e,
22
- documentModifier: t,
23
- products: c,
24
- onApplied: () => {
25
- f = !0;
26
- }
27
- }) || (f = N({ currentNode: e, documentModifier: t, products: c })), !f && d && t.apply(new g(l)), !0;
34
+ const c = n.getDocumentModifier(), d = m.getConfig(r), g = Object.keys(t).some((l) => JSON.stringify(d[l]) !== JSON.stringify(t[l]));
35
+ if (m.stageConfig(c, r, t), C(r))
36
+ return c.apply(new s(i)), !0;
37
+ let u = !1;
38
+ return S(e, () => {
39
+ p({
40
+ currentNode: r,
41
+ documentModifier: c,
42
+ products: o,
43
+ onApplied: () => {
44
+ u = !0;
45
+ }
46
+ }) || (u = y({ currentNode: r, documentModifier: c, products: o }));
47
+ }), !u && g && c.apply(new s(i)), !0;
28
48
  }
29
49
  export {
30
- P as commitBlockChangeOutsidePanel,
31
- D as registerNodeConfigWriter
50
+ $ as commitBlockChangeOutsidePanel,
51
+ P as readBlockNodeConfigs,
52
+ B as registerNodeConfigWriter
32
53
  };
@@ -1,24 +1,47 @@
1
- import { useRecommendationExtensionStore as d } from "../store/recommendation.js";
2
- import { commitBlockChangeOutsidePanel as f } from "./nodeConfigWriter.js";
3
- async function h(e, r) {
4
- const t = d(), o = t.blockStates[e];
5
- if (!o)
1
+ import { useRecommendationExtensionStore as g } from "../store/recommendation.js";
2
+ import { toBlockUrlConfig as m } from "./blockUrlConfig.js";
3
+ import { commitBlockChangeOutsidePanel as d, readBlockNodeConfigs as y } from "./nodeConfigWriter.js";
4
+ function l(t, o) {
5
+ const r = JSON.stringify(m(o));
6
+ return y(t).find((e) => JSON.stringify(m(e)) !== r) ?? o;
7
+ }
8
+ async function C(t, o, r) {
9
+ const e = g(), c = [t, o];
10
+ (/* @__PURE__ */ new Map([[t, l(t, r)], [o, r]])).forEach((n, s) => {
11
+ var a;
12
+ (!!n.strategyId || s === o) && ((a = e.blockStates[s]) == null ? void 0 : a.recommendationConfigs.strategyId) !== n.strategyId && e.seedBlockUrlConfig(s, m(n));
13
+ }), e.resetArrivedBlock(o), await Promise.all(c.map(async (n) => {
14
+ if (!await e.refreshStrategyBlock(n))
15
+ return;
16
+ const { recommendationConfigs: s, recommendationProducts: i } = e.blockStates[n];
17
+ d(
18
+ n,
19
+ { size: s.size },
20
+ i,
21
+ "Refresh duplicated recommendation strategy"
22
+ );
23
+ }));
24
+ }
25
+ async function B(t, o) {
26
+ const r = g(), e = r.blockStates[t];
27
+ if (!e)
6
28
  return !1;
7
- const { strategyId: a, strategy: c, size: i, nodeConfigVersion: s } = o.recommendationConfigs;
8
- if (t.currentRecommendationId === e)
29
+ const { strategyId: c, strategy: f, size: n, nodeConfigVersion: s } = e.recommendationConfigs;
30
+ if (r.currentRecommendationId === t)
9
31
  try {
10
- await t.fetchRecommendationProducts();
11
- } catch (m) {
12
- console.error("[recommendation] product refetch after a strategy change failed", m);
32
+ await r.fetchRecommendationProducts();
33
+ } catch (a) {
34
+ console.error("[recommendation] product refetch after a strategy change failed", a);
13
35
  }
14
- const n = f(
15
- e,
16
- { strategyId: a, strategy: c, size: i, configVersion: s },
17
- o.recommendationProducts,
18
- r
36
+ const i = d(
37
+ t,
38
+ { strategyId: c, strategy: f, size: n, configVersion: s },
39
+ e.recommendationProducts,
40
+ o
19
41
  );
20
- return n || console.error("[recommendation] strategy change did not reach a node", { blockId: e }), n;
42
+ return i || console.error("[recommendation] strategy change did not reach a node", { blockId: t }), i;
21
43
  }
22
44
  export {
23
- h as commitStrategyChange
45
+ B as commitStrategyChange,
46
+ C as refreshDuplicatedStrategyBlocks
24
47
  };
@@ -10,3 +10,8 @@ export declare const MSO_ITEMS_HIDE_END = "MSO_ITEMS_HIDE_END";
10
10
  * and collapse them inline for web-engine Outlook.
11
11
  */
12
12
  export declare function wrapHiddenPriceTdsForOutlook(html: string): string;
13
+ /**
14
+ * Export safety net for a zero Items width: the wrapper gets the 100% pin, the cell's zero is
15
+ * dropped so it keeps its column width (a pin there overflows 33%/50% columns).
16
+ */
17
+ export declare function normalizeItemsBlockWidths(html: string): string;
@@ -46,6 +46,11 @@ export declare class ItemsBlock extends Block {
46
46
  * relies on this td-level fallback in pairProductVariables.
47
47
  */
48
48
  private _stampProductTdDomAttributes;
49
+ /**
50
+ * Repairs a saved module's collapsed card into the state the load migrator produces. Acts only
51
+ * on a zero width, so Stripo's non-zero drop-time width never triggers it and a healed block no-ops.
52
+ */
53
+ private _healCollapsedWidths;
49
54
  /**
50
55
  * Whether a currency self-heal pass should run for this block (SD-147588). The only gate here is
51
56
  * once-per-session: heal each instance at most once, so the auto-fix can't fight undo (undo
@@ -0,0 +1,12 @@
1
+ export declare const PRODUCT_TD_SELECTOR = ".ins-product-td";
2
+ export declare const PRODUCT_WRAPPER_SELECTOR = ".ins-product-wrapper";
3
+ /** The declaration `layouts/vertical.html` and `layouts/horizontal.html` ship inline. */
4
+ export declare const WIDTH_PIN_DECLARATION = "width: 100%";
5
+ /** True for a `width` attribute value that renders the card zero-wide (`0`, `0px`, `0%`). */
6
+ export declare const isZeroWidthValue: (value?: string | null) => boolean;
7
+ /** True when an inline style declares a zero `width` (ignores `max-width` / `min-width`). */
8
+ export declare const hasZeroWidthDeclaration: (style: string) => boolean;
9
+ /** Rewrites a zero width to the pin or appends it; a non-zero width is kept. Idempotent. */
10
+ export declare const withWidthPin: (style: string) => string;
11
+ /** Drops a zero `width` declaration; the product cell must inherit its column width, not be pinned. */
12
+ export declare const withoutZeroWidth: (style: string) => string;