@useinsider/guido 2.1.0-beta.42f5dfa → 2.1.0-beta.486ee9c

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 (112) hide show
  1. package/README.md +36 -0
  2. package/dist/@types/config/schemas.js +70 -65
  3. package/dist/components/Guido.vue.js +1 -1
  4. package/dist/components/Guido.vue2.js +69 -58
  5. package/dist/components/organisms/base/Toaster.vue.js +4 -4
  6. package/dist/components/organisms/base/Toaster.vue2.js +12 -9
  7. package/dist/components/organisms/email-preview/desktop-preview/EmailSizeIndicator.vue.js +5 -5
  8. package/dist/components/organisms/email-preview/desktop-preview/EmailSizeIndicator.vue2.js +2 -2
  9. package/dist/components/organisms/extensions/recommendation/FilterItem.vue.js +11 -13
  10. package/dist/components/organisms/extensions/recommendation/FilterItem.vue2.js +54 -23
  11. package/dist/components/organisms/extensions/recommendation/FilterSelectionDrawer.vue.js +7 -5
  12. package/dist/components/organisms/extensions/recommendation/FilterSelectionDrawer.vue2.js +34 -21
  13. package/dist/components/organisms/extensions/recommendation/Filters.vue.js +11 -11
  14. package/dist/components/organisms/extensions/recommendation/Filters.vue2.js +48 -36
  15. package/dist/components/organisms/extensions/recommendation/LogicAdapter.vue2.js +11 -9
  16. package/dist/components/organisms/header/HeaderWrapper.vue.js +9 -9
  17. package/dist/components/organisms/unsubscribe/UnsubscribePageSelection.vue.js +1 -1
  18. package/dist/components/organisms/unsubscribe/UnsubscribePageSelection.vue2.js +19 -19
  19. package/dist/composables/useBlocksConfig.js +26 -16
  20. package/dist/composables/useHtmlValidator.js +107 -119
  21. package/dist/composables/useRecommendation.js +9 -9
  22. package/dist/composables/useStripo.js +25 -23
  23. package/dist/composables/useVersionHistoryApi.js +1 -1
  24. package/dist/config/compiler/utils/recommendationCompilerUtils.js +28 -23
  25. package/dist/config/i18n/en/index.js +11 -0
  26. package/dist/config/i18n/en/labels.json.js +7 -0
  27. package/dist/config/i18n/en/toasters.json.js +56 -0
  28. package/dist/config/i18n/en/tooltips.json.js +82 -0
  29. package/dist/config/i18n/index.js +7 -0
  30. package/dist/config/migrator/itemsBlockMigrator.js +127 -122
  31. package/dist/config/migrator/recommendationMigrator.js +1 -1
  32. package/dist/enums/defaults.js +8 -4
  33. package/dist/extensions/Blocks/Recommendation/block.js +26 -23
  34. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +37 -33
  35. package/dist/extensions/Blocks/Recommendation/constants/layout.js +16 -12
  36. package/dist/extensions/Blocks/Recommendation/constants/selectors.js +15 -11
  37. package/dist/extensions/Blocks/Recommendation/controls/button/index.js +9 -9
  38. package/dist/extensions/Blocks/Recommendation/controls/image/index.js +1 -1
  39. package/dist/extensions/Blocks/Recommendation/controls/layout/index.js +41 -29
  40. package/dist/extensions/Blocks/Recommendation/controls/main/algorithm.js +16 -16
  41. package/dist/extensions/Blocks/Recommendation/controls/main/currency.js +30 -32
  42. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +217 -94
  43. package/dist/extensions/Blocks/Recommendation/controls/main/locale.js +9 -9
  44. package/dist/extensions/Blocks/Recommendation/controls/main/productLayout.js +97 -39
  45. package/dist/extensions/Blocks/Recommendation/controls/main/shuffle.js +16 -16
  46. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +304 -209
  47. package/dist/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.js +21 -0
  48. package/dist/extensions/Blocks/Recommendation/controls/name/index.js +10 -10
  49. package/dist/extensions/Blocks/Recommendation/controls/name/textTrim.js +5 -5
  50. package/dist/extensions/Blocks/Recommendation/controls/oldPrice/index.js +14 -14
  51. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/index.js +9 -9
  52. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textAfter.js +3 -3
  53. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textBefore.js +1 -1
  54. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/index.js +9 -9
  55. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textAfter.js +3 -3
  56. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textBefore.js +13 -13
  57. package/dist/extensions/Blocks/Recommendation/controls/price/index.js +3 -3
  58. package/dist/extensions/Blocks/Recommendation/controls/spacing/index.js +223 -99
  59. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +237 -140
  60. package/dist/extensions/Blocks/Recommendation/templates/grid/elementRenderer.js +27 -32
  61. package/dist/extensions/Blocks/Recommendation/templates/grid/migration.js +1 -1
  62. package/dist/extensions/Blocks/Recommendation/templates/grid/template.js +27 -30
  63. package/dist/extensions/Blocks/Recommendation/templates/list/elementRenderer.js +22 -27
  64. package/dist/extensions/Blocks/Recommendation/templates/utils.js +50 -38
  65. package/dist/extensions/Blocks/Recommendation/utils/filterUtil.js +8 -8
  66. package/dist/extensions/Blocks/Recommendation/validation/filterSchema.js +29 -0
  67. package/dist/extensions/Blocks/Unsubscribe/block.js +29 -29
  68. package/dist/extensions/Blocks/Unsubscribe/control.js +12 -9
  69. package/dist/extensions/Blocks/Unsubscribe/elements/preview.js +13 -11
  70. package/dist/extensions/Blocks/Unsubscribe/styles.css.js +31 -1
  71. package/dist/extensions/ModulesTabIcons/extension.js +17 -0
  72. package/dist/guido.css +1 -1
  73. package/dist/node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js +257 -187
  74. package/dist/services/recommendationApi.js +11 -9
  75. package/dist/services/stripoApi.js +20 -17
  76. package/dist/src/@types/config/schemas.d.ts +8 -0
  77. package/dist/src/components/organisms/extensions/recommendation/FilterItem.vue.d.ts +1 -0
  78. package/dist/src/components/organisms/extensions/recommendation/Filters.vue.d.ts +17 -1
  79. package/dist/src/composables/useConfig.d.ts +4 -0
  80. package/dist/src/config/i18n/en/index.d.ts +1 -0
  81. package/dist/src/config/i18n/index.d.ts +16 -0
  82. package/dist/src/enums/defaults.d.ts +4 -0
  83. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +6 -0
  84. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +3 -3
  85. package/dist/src/extensions/Blocks/Recommendation/constants/layout.d.ts +12 -2
  86. package/dist/src/extensions/Blocks/Recommendation/constants/selectors.d.ts +12 -1
  87. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +50 -11
  88. package/dist/src/extensions/Blocks/Recommendation/controls/main/productLayout.d.ts +22 -4
  89. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +36 -16
  90. package/dist/src/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.d.ts +29 -0
  91. package/dist/src/extensions/Blocks/Recommendation/controls/spacing/index.d.ts +50 -17
  92. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +26 -2
  93. package/dist/src/extensions/Blocks/Recommendation/templates/grid/migration.d.ts +1 -1
  94. package/dist/src/extensions/Blocks/Recommendation/templates/grid/template.d.ts +1 -1
  95. package/dist/src/extensions/Blocks/Recommendation/templates/index.d.ts +4 -0
  96. package/dist/src/extensions/Blocks/Recommendation/templates/list/migration.d.ts +1 -1
  97. package/dist/src/extensions/Blocks/Recommendation/templates/utils.d.ts +12 -3
  98. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +14 -0
  99. package/dist/src/extensions/Blocks/Recommendation/validation/filterSchema.d.ts +15 -0
  100. package/dist/src/extensions/Blocks/Unsubscribe/control.d.ts +1 -0
  101. package/dist/src/extensions/ModulesTabIcons/extension.d.ts +2 -0
  102. package/dist/src/stores/config.d.ts +36 -0
  103. package/dist/static/styles/components/notification.css.js +19 -0
  104. package/dist/static/styles/components/tools.css.js +6 -2
  105. package/dist/static/styles/components/version-history.css.js +10 -2
  106. package/dist/static/styles/components/wide-panel.css.js +18 -2
  107. package/dist/static/styles/customEditorStyle.css.js +59 -31
  108. package/dist/static/styles/variables.css.js +2 -0
  109. package/dist/static/templates/empty/index.html.js +74 -0
  110. package/dist/static/templates/empty/style.css.js +779 -0
  111. package/dist/stores/unsubscribe.js +37 -34
  112. package/package.json +2 -2
@@ -1,32 +1,32 @@
1
- var d = Object.defineProperty;
2
- var m = (l, n, t) => n in l ? d(l, n, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[n] = t;
3
- var i = (l, n, t) => m(l, typeof n != "symbol" ? n + "" : n, t);
4
- import { CommonControl as h } from "../../../common-control.js";
5
- import { useRecommendationExtensionStore as C } from "../../store/recommendation.js";
6
- import { AlgorithmControl as f } from "./algorithm.js";
7
- import { ALGORITHM_CONTROL_ID as M } from "./algorithm.js";
8
- import { CurrencyControl as p } from "./currency.js";
9
- import { CURRENCY_CONTROL_ID as W } from "./currency.js";
10
- import { FiltersControl as g } from "./filters.js";
11
- import { FILTERS_CONTROL_ID as H } from "./filters.js";
12
- import { LocaleControl as R } from "./locale.js";
13
- import { LOCALE_CONTROL_ID as j } from "./locale.js";
14
- import { ProductLayoutControl as _ } from "./productLayout.js";
15
- import { PRODUCT_LAYOUT_CONTROL_ID as K } from "./productLayout.js";
16
- import { ShuffleControl as N } from "./shuffle.js";
17
- import { SHUFFLE_CONTROL_ID as J } from "./shuffle.js";
18
- import { regenerateProductRowsWithStyles as P, getBlockElement as b, updateProductContentInPlace as y } from "./utils.js";
19
- import { formatProductPrice as X, getCardComposition as Z, getCurrentLayout as tt, reapplySpacing as et, regenerateProductRows as ot, setCurrencyAttributes as rt, updatePricesInPlace as it, updateSingleProductContent as nt } from "./utils.js";
20
- import { useDebounceFn as c } from "../../../../../node_modules/@vueuse/shared/index.js";
21
- const T = "recommendation-id", L = "ui-elements-recommendation-block";
22
- class x extends h {
1
+ var g = Object.defineProperty;
2
+ var C = (d, c, t) => c in d ? g(d, c, { enumerable: !0, configurable: !0, writable: !0, value: t }) : d[c] = t;
3
+ var i = (d, c, t) => C(d, typeof c != "symbol" ? c + "" : c, t);
4
+ import { CommonControl as p } from "../../../common-control.js";
5
+ import { DEFAULT_NODE_CONFIG as a } from "../../constants/defaultConfig.js";
6
+ import { RecommendationConfigService as m } from "../../services/configService.js";
7
+ import { useRecommendationExtensionStore as y } from "../../store/recommendation.js";
8
+ import { AlgorithmControl as R } from "./algorithm.js";
9
+ import { ALGORITHM_CONTROL_ID as H } from "./algorithm.js";
10
+ import { CurrencyControl as N } from "./currency.js";
11
+ import { CURRENCY_CONTROL_ID as K } from "./currency.js";
12
+ import { FiltersControl as b } from "./filters.js";
13
+ import { FILTERS_CONTROL_ID as J } from "./filters.js";
14
+ import { LocaleControl as _ } from "./locale.js";
15
+ import { LOCALE_CONTROL_ID as X } from "./locale.js";
16
+ import { ProductLayoutControl as I } from "./productLayout.js";
17
+ import { PRODUCT_LAYOUT_CONTROL_ID as tt } from "./productLayout.js";
18
+ import { ShuffleControl as S } from "./shuffle.js";
19
+ import { SHUFFLE_CONTROL_ID as ot } from "./shuffle.js";
20
+ import { getBlockElement as P, updateProductContentInPlace as T, regenerateProductRowsWithStyles as L } from "./utils.js";
21
+ import { adjustProductsToSize as nt, formatProductPrice as st, getCardComposition as it, getCurrentLayout as at, reapplySpacing as ct, regenerateMobileProductRows as lt, regenerateProductRows as ut, setCurrencyAttributes as dt, updatePricesInPlace as mt, updateSingleProductContent as ht } from "./utils.js";
22
+ import { useDebounceFn as h } from "../../../../../node_modules/@vueuse/shared/index.js";
23
+ const k = "recommendation-id", D = "ui-elements-recommendation-block";
24
+ class W extends p {
23
25
  constructor() {
24
26
  super(...arguments);
25
- i(this, "store", C());
27
+ i(this, "store", y());
26
28
  i(this, "storeUnsubscription", () => {
27
29
  });
28
- // Track if initial data has been fetched per block ID to avoid redundant API calls
29
- i(this, "initializedBlocks", /* @__PURE__ */ new Map());
30
30
  // Sub-control instances for lifecycle management
31
31
  i(this, "algorithmControl", null);
32
32
  i(this, "localeControl", null);
@@ -37,30 +37,41 @@ class x extends h {
37
37
  /**
38
38
  * Debounced product fetch to prevent rapid API calls during config changes
39
39
  */
40
- i(this, "_debouncedFetchProducts", c(() => {
40
+ i(this, "_debouncedFetchProducts", h(() => {
41
41
  this.store.fetchRecommendationProducts();
42
42
  }, 500));
43
43
  /**
44
- * Debounced regeneration when products arrive from API
45
- * Tries in-place update first to preserve styles, falls back to full regeneration
44
+ * Debounced content update when products arrive from API.
45
+ *
46
+ * Tries in-place update first (preserves user-applied styles) — this succeeds
47
+ * when the product count matches the DOM (algorithm/locale/currency changes).
48
+ *
49
+ * Falls back to full regeneration when product count differs from DOM — this
50
+ * happens after "Number of Products" changes where the DOM still has the old
51
+ * count. The store pads products to the configured size, so in-place only
52
+ * fails when the size actually changed.
46
53
  */
47
- i(this, "_debouncedRegenerateWithProducts", c(() => {
54
+ i(this, "_debouncedRegenerateWithProducts", h(() => {
48
55
  const t = this.store.recommendationProducts;
49
56
  if (!this.currentNode || !this.api)
50
57
  return;
51
58
  const e = this.api.getDocumentModifier();
52
- y({
59
+ T({
53
60
  currentNode: this.currentNode,
54
61
  documentModifier: e,
55
62
  products: t
56
- }) || this._regenerateWithProducts(t);
63
+ }) || L({
64
+ currentNode: this.currentNode,
65
+ documentModifier: e,
66
+ products: t
67
+ });
57
68
  }, 100));
58
69
  }
59
70
  getId() {
60
- return L;
71
+ return D;
61
72
  }
62
73
  getTemplate() {
63
- return this.algorithmControl = new f(), this.localeControl = new R(), this.currencyControl = new p(), this.productLayoutControl = new _(), this.filtersControl = new g(), this.shuffleControl = new N(), `
74
+ return this.algorithmControl = new R(), this.localeControl = new _(), this.currencyControl = new N(), this.productLayoutControl = new I(), this.filtersControl = new b(), this.shuffleControl = new S(), `
64
75
  <div class="recommendation-controls-container">
65
76
  ${this.algorithmControl.getTemplate()}
66
77
  ${this.localeControl.getTemplate()}
@@ -72,41 +83,28 @@ class x extends h {
72
83
  `;
73
84
  }
74
85
  async onRender() {
86
+ var e;
75
87
  const t = this._getRecommendationIdFromNode(this.currentNode) ?? this.store.currentRecommendationId;
76
- if (t !== null && this.store.setCurrentBlock(t), this._listenStateUpdates(), t !== null && this.initializedBlocks.get(t)) {
88
+ if (t !== null && this.store.setCurrentBlock(t), this._listenStateUpdates(), t !== null && ((e = this.store.blockStates[t]) != null && e.isInitialized)) {
77
89
  this._initializeSubControls();
78
90
  return;
79
91
  }
80
92
  await this._fetchBlockData(t), this._initializeSubControls();
81
93
  }
82
- /**
83
- * Immediately regenerate products with styles (no debounce)
84
- * Used for initial load after fetch completes
85
- */
86
- _regenerateWithProducts(t) {
87
- if (!this.currentNode || !this.api) {
88
- console.warn("[Recommendation] Cannot regenerate - missing currentNode or api");
89
- return;
90
- }
91
- P({
92
- currentNode: this.currentNode,
93
- documentModifier: this.api.getDocumentModifier(),
94
- products: t
95
- });
96
- }
97
94
  onTemplateNodeUpdated(t) {
95
+ var r;
98
96
  super.onTemplateNodeUpdated(t);
99
97
  const e = this._getRecommendationIdFromNode(t);
100
- e !== null && e !== this.store.currentRecommendationId && this.store.setCurrentBlock(e), e !== null && !this.initializedBlocks.get(e) && this._fetchBlockData(e), [
98
+ e !== null && e !== this.store.currentRecommendationId && this.store.setCurrentBlock(e), this._syncNodeConfigToStore(), e !== null && !((r = this.store.blockStates[e]) != null && r.isInitialized) && this._fetchBlockData(e), [
101
99
  this.algorithmControl,
102
100
  this.localeControl,
103
101
  this.currencyControl,
104
102
  this.productLayoutControl,
105
103
  this.filtersControl,
106
104
  this.shuffleControl
107
- ].forEach((r) => {
105
+ ].forEach((n) => {
108
106
  var s;
109
- r != null && r.api && (r.currentNode = t, (s = r.onTemplateNodeUpdated) == null || s.call(r, t));
107
+ n != null && n.api && (n.currentNode = t, (s = n.onTemplateNodeUpdated) == null || s.call(n, t));
110
108
  });
111
109
  }
112
110
  onDestroy() {
@@ -140,31 +138,128 @@ class x extends h {
140
138
  });
141
139
  }
142
140
  /**
143
- * Fetches initial data for a block (filters, algorithms, products).
141
+ * Syncs persisted node config into the Pinia store's block state.
142
+ *
143
+ * setCurrentBlock() creates a default entry (USD, en_US, mostPopular).
144
+ * For saved templates, the real config lives in the node (e.g., TRY, tr_TR).
145
+ * This method reads it and patches the store so fetchRecommendationProducts()
146
+ * uses the correct values.
147
+ *
148
+ * Uses triggerRefetch: false because the fetch hasn't happened yet —
149
+ * values are being prepared for the upcoming initial fetch.
150
+ */
151
+ _syncNodeConfigToStore() {
152
+ var r;
153
+ const t = m.getConfig(this.currentNode), e = this.store.currentRecommendationId, o = e !== null && ((r = this.store.blockStates[e]) == null ? void 0 : r.isInitialized);
154
+ this.store.patchCurrentBlockConfig({
155
+ strategy: t.strategy,
156
+ language: t.language,
157
+ size: t.size,
158
+ productIds: t.productIds,
159
+ // Only sync filters from node config during initial load.
160
+ // After initialization, the Pinia store is the source of truth
161
+ // for filters (edited via the filter drawer).
162
+ ...o ? {} : { filters: t.filters },
163
+ shuffleProducts: t.shuffleProducts,
164
+ currencySettings: {
165
+ name: t.currency.code,
166
+ value: t.currency.code,
167
+ symbol: t.currency.symbol,
168
+ alignment: t.currency.alignment === "before" ? "0" : "1",
169
+ decimalCount: t.currency.decimalCount.toString(),
170
+ decimalSeparator: t.currency.decimalSeparator,
171
+ thousandSeparator: t.currency.thousandSeparator
172
+ }
173
+ }, { triggerRefetch: !1 });
174
+ }
175
+ /**
176
+ * Fetches initial data for a block in three phases:
177
+ * 1. Shared reference data (algorithms, currencies, filters) — parallel
178
+ * 2. Smart defaults for new blocks (currency, algorithm) — sequential
179
+ * 3. Product data with correct defaults — sequential
180
+ *
144
181
  * Shared by onRender() and onTemplateNodeUpdated() to avoid duplication.
145
182
  * Marks the block as initialized to prevent redundant fetches on re-selection.
146
183
  */
147
184
  async _fetchBlockData(t) {
148
- t !== null && this.initializedBlocks.set(t, !0), (await Promise.allSettled([
149
- this.store.fetchRecommendationFilters(),
185
+ t !== null && this.store.markBlockInitialized(t), (await Promise.allSettled([
150
186
  this.store.fetchRecommendationCreateData(),
151
- this.store.fetchRecommendationProducts()
187
+ this.store.fetchRecommendationFilters()
152
188
  ])).forEach((o, r) => {
153
- o.status === "rejected" && console.warn(`Recommendation block: ${[
154
- "fetchRecommendationFilters",
155
- "fetchRecommendationCreateData",
156
- "fetchRecommendationProducts"
157
- ][r]} failed`, o.reason);
158
- });
189
+ o.status === "rejected" && console.warn(`Recommendation block: ${["fetchRecommendationCreateData", "fetchRecommendationFilters"][r]} failed`, o.reason);
190
+ }), this._applySmartDefaults();
191
+ try {
192
+ await this.store.fetchRecommendationProducts();
193
+ } catch (o) {
194
+ console.warn("Recommendation block: fetchRecommendationProducts failed", o);
195
+ }
196
+ }
197
+ /**
198
+ * Applies smart defaults for newly dropped blocks.
199
+ *
200
+ * For new blocks (config still matches hardcoded defaults), validates that
201
+ * the default currency and algorithm are available from the API response.
202
+ * If not, falls back to the first available option.
203
+ *
204
+ * Saved templates with user-customized config are left unchanged because
205
+ * their values won't match the hardcoded defaults.
206
+ */
207
+ _applySmartDefaults() {
208
+ if (!this.currentNode || !this.api)
209
+ return;
210
+ const t = m.getConfig(this.currentNode), e = {};
211
+ let o = null, r = null, n = null;
212
+ if (t.currency.code === a.currency.code) {
213
+ const { currencyList: s } = this.store;
214
+ s.length > 0 && (s.some(
215
+ (u) => u.value === `price.${a.currency.code}`
216
+ ) || (o = s[0].value.replace("price.", ""), e.currency = {
217
+ ...a.currency,
218
+ code: o,
219
+ symbol: o
220
+ }));
221
+ }
222
+ if (t.strategy === a.strategy) {
223
+ const s = this.store.getActivePredictiveAlgorithms;
224
+ s.length > 0 && (s.some(
225
+ (u) => u.value === a.strategy
226
+ ) || (r = s[0].value, e.strategy = r));
227
+ }
228
+ if (t.language === a.language) {
229
+ const s = this.store.getLanguages;
230
+ s.length > 0 && (s.some(
231
+ (u) => u.value === a.language
232
+ ) || (n = s[0].value, e.language = n));
233
+ }
234
+ !o && !r && !n || (m.updateConfig(
235
+ this.api,
236
+ this.currentNode,
237
+ e,
238
+ "Applied smart defaults"
239
+ ), this.store.patchCurrentBlockConfig({
240
+ ...o ? {
241
+ currencySettings: {
242
+ name: o,
243
+ value: o,
244
+ symbol: o,
245
+ alignment: a.currency.alignment === "before" ? "0" : "1",
246
+ decimalCount: a.currency.decimalCount.toString(),
247
+ decimalSeparator: a.currency.decimalSeparator,
248
+ thousandSeparator: a.currency.thousandSeparator
249
+ }
250
+ } : {},
251
+ ...r ? { strategy: r } : {},
252
+ ...n ? { language: n } : {}
253
+ }, { triggerRefetch: !1 }));
159
254
  }
160
255
  /**
161
256
  * Reads the recommendation-id attribute from the block element within the node
162
257
  */
163
258
  _getRecommendationIdFromNode(t) {
164
- const e = b(t);
259
+ const e = P(t);
165
260
  if (!e || !("getAttribute" in e))
166
261
  return null;
167
- const o = e.getAttribute(T);
262
+ const o = e.getAttribute(k);
168
263
  if (!o)
169
264
  return null;
170
265
  const r = parseInt(o);
@@ -176,42 +271,70 @@ class x extends h {
176
271
  * Uses configVersion counter (incremented only by patchCurrentBlockConfig)
177
272
  * to distinguish user-initiated config changes from internal mutations
178
273
  * (e.g., fetchRecommendationCreateData setting preferred currency).
274
+ *
275
+ * Tracks currentRecommendationId to detect block switches. When the user
276
+ * selects a different recommendation block, the proxy getters (e.g.,
277
+ * recommendationProducts) return the new block's data — a different array
278
+ * reference that would be falsely detected as "new products arrived".
279
+ * We skip that tick and update tracking references instead.
179
280
  */
180
281
  _listenStateUpdates() {
181
282
  const { store: t } = this;
182
- let e = t.recommendationProducts, o = t.$state.configVersion;
283
+ let e = t.recommendationProducts, o = t.$state.configVersion, r = t.currentRecommendationId;
183
284
  this.storeUnsubscription = t.$subscribe(() => {
184
- const r = t.$state.configVersion;
185
- r !== o && (o = r, this._debouncedFetchProducts());
186
- const s = t.recommendationProducts, a = s !== e, u = Array.isArray(s) && s.length > 0;
187
- a && u && (e = s, this._debouncedRegenerateWithProducts());
285
+ const n = t.currentRecommendationId;
286
+ if (n !== r) {
287
+ r = n, e = t.recommendationProducts, o = t.$state.configVersion;
288
+ return;
289
+ }
290
+ const s = t.$state.configVersion;
291
+ s !== o && (o = s, this._persistFiltersToNodeConfig(), this._debouncedFetchProducts());
292
+ const l = t.recommendationProducts, u = l !== e, f = Array.isArray(l) && l.length > 0;
293
+ u && f && (e = l, this._debouncedRegenerateWithProducts());
188
294
  });
189
295
  }
296
+ /**
297
+ * Persists the current filter state from Pinia store to the Stripo node config.
298
+ * This ensures filters survive template save/reload cycles.
299
+ */
300
+ _persistFiltersToNodeConfig() {
301
+ if (!this.currentNode || !this.api)
302
+ return;
303
+ const { filters: t } = this.store.recommendationConfigs;
304
+ m.updateConfig(
305
+ this.api,
306
+ this.currentNode,
307
+ { filters: t },
308
+ "Update recommendation filters"
309
+ );
310
+ }
190
311
  }
191
312
  export {
192
- M as ALGORITHM_CONTROL_ID,
193
- f as AlgorithmControl,
194
- L as CONTROL_BLOCK_ID,
195
- W as CURRENCY_CONTROL_ID,
196
- p as CurrencyControl,
197
- H as FILTERS_CONTROL_ID,
198
- g as FiltersControl,
199
- j as LOCALE_CONTROL_ID,
200
- R as LocaleControl,
201
- K as PRODUCT_LAYOUT_CONTROL_ID,
202
- _ as ProductLayoutControl,
203
- x as RecommendationBlockControl,
204
- J as SHUFFLE_CONTROL_ID,
205
- N as ShuffleControl,
206
- X as formatProductPrice,
207
- b as getBlockElement,
208
- Z as getCardComposition,
209
- tt as getCurrentLayout,
210
- et as reapplySpacing,
211
- ot as regenerateProductRows,
212
- P as regenerateProductRowsWithStyles,
213
- rt as setCurrencyAttributes,
214
- it as updatePricesInPlace,
215
- y as updateProductContentInPlace,
216
- nt as updateSingleProductContent
313
+ H as ALGORITHM_CONTROL_ID,
314
+ R as AlgorithmControl,
315
+ D as CONTROL_BLOCK_ID,
316
+ K as CURRENCY_CONTROL_ID,
317
+ N as CurrencyControl,
318
+ J as FILTERS_CONTROL_ID,
319
+ b as FiltersControl,
320
+ X as LOCALE_CONTROL_ID,
321
+ _ as LocaleControl,
322
+ tt as PRODUCT_LAYOUT_CONTROL_ID,
323
+ I as ProductLayoutControl,
324
+ W as RecommendationBlockControl,
325
+ ot as SHUFFLE_CONTROL_ID,
326
+ S as ShuffleControl,
327
+ nt as adjustProductsToSize,
328
+ st as formatProductPrice,
329
+ P as getBlockElement,
330
+ it as getCardComposition,
331
+ at as getCurrentLayout,
332
+ ct as reapplySpacing,
333
+ lt as regenerateMobileProductRows,
334
+ ut as regenerateProductRows,
335
+ L as regenerateProductRowsWithStyles,
336
+ dt as setCurrencyAttributes,
337
+ mt as updatePricesInPlace,
338
+ T as updateProductContentInPlace,
339
+ ht as updateSingleProductContent
217
340
  };
@@ -2,20 +2,20 @@ var s = Object.defineProperty;
2
2
  var r = (o, t, e) => t in o ? s(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
3
3
  var a = (o, t, e) => r(o, typeof t != "symbol" ? t + "" : t, e);
4
4
  import { UEAttr as l } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
5
- import { CommonControl as m } from "../../../common-control.js";
5
+ import { CommonControl as c } from "../../../common-control.js";
6
6
  import { RecommendationConfigService as i } from "../../services/configService.js";
7
- import { useRecommendationExtensionStore as c } from "../../store/recommendation.js";
8
- const g = "recommendation-locale-control", n = {
7
+ import { useRecommendationExtensionStore as g } from "../../store/recommendation.js";
8
+ const m = "recommendation-locale-control", n = {
9
9
  LOCALE: "language"
10
10
  };
11
- class C extends m {
11
+ class E extends c {
12
12
  constructor() {
13
13
  super(...arguments);
14
14
  // Store is used ONLY for API-fetched data (language options), not for config
15
- a(this, "store", c());
15
+ a(this, "store", g());
16
16
  }
17
17
  getId() {
18
- return g;
18
+ return m;
19
19
  }
20
20
  getTemplate() {
21
21
  return `
@@ -51,7 +51,7 @@ class C extends m {
51
51
  );
52
52
  }
53
53
  _onLocaleChange(e) {
54
- this.currentNode && (i.updateConfig(
54
+ !this.currentNode || i.getConfig(this.currentNode).language === e || (i.updateConfig(
55
55
  this.api,
56
56
  this.currentNode,
57
57
  { language: e },
@@ -65,6 +65,6 @@ class C extends m {
65
65
  }
66
66
  }
67
67
  export {
68
- g as LOCALE_CONTROL_ID,
69
- C as LocaleControl
68
+ m as LOCALE_CONTROL_ID,
69
+ E as LocaleControl
70
70
  };
@@ -1,45 +1,60 @@
1
- var d = Object.defineProperty;
2
- var a = (i, e, t) => e in i ? d(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
- var n = (i, e, t) => a(i, typeof e != "symbol" ? e + "" : e, t);
4
- import { CommonControl as c } from "../../../common-control.js";
5
- import { MAX_PRODUCT_COUNT as _, MAX_PRODUCTS_PER_ROW as h } from "../../constants/layout.js";
6
- import { RecommendationConfigService as r } from "../../services/configService.js";
7
- import { useRecommendationExtensionStore as R } from "../../store/recommendation.js";
8
- import { getCurrentLayout as C, regenerateProductRowsWithStyles as p } from "./utils.js";
9
- import { useDebounceFn as m } from "../../../../../node_modules/@vueuse/shared/index.js";
10
- const l = "recommendation-product-layout-control", o = {
1
+ var u = Object.defineProperty;
2
+ var _ = (n, r, t) => r in n ? u(n, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[r] = t;
3
+ var a = (n, r, t) => _(n, typeof r != "symbol" ? r + "" : r, t);
4
+ import { EditorStatePropertyType as c, PreviewDeviceMode as C } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
5
+ import { CommonControl as R } from "../../../common-control.js";
6
+ import { MAX_PRODUCT_COUNT as h, MAX_PRODUCTS_PER_ROW as p, MAX_MOBILE_PRODUCTS_PER_ROW as g, DEFAULT_COLUMN_SPACING as m, DEFAULT_MOBILE_COLUMN_SPACING as O } from "../../constants/layout.js";
7
+ import { RecommendationConfigService as i } from "../../services/configService.js";
8
+ import { useRecommendationExtensionStore as l } from "../../store/recommendation.js";
9
+ import { ensureMobileCssRulesExist as I } from "../mobileLayout/cssRules.js";
10
+ import { getCurrentLayout as N, regenerateMobileProductRows as f, adjustProductsToSize as b, regenerateProductRowsWithStyles as P } from "./utils.js";
11
+ import { useDebounceFn as L } from "../../../../../node_modules/@vueuse/shared/index.js";
12
+ const M = "recommendation-product-layout-control", e = {
11
13
  PRODUCT_COUNT: "size",
12
14
  PRODUCT_IN_ROW: "cardsInRow",
13
- PRODUCT_IN_ROW_LABEL: "cardsInRowLabel"
15
+ PRODUCT_IN_ROW_LABEL: "cardsInRowLabel",
16
+ MOBILE_CARDS_IN_ROW: "mobileCardsInRow",
17
+ MOBILE_CARDS_IN_ROW_LABEL: "mobileCardsInRowLabel"
14
18
  };
15
- class I extends c {
19
+ class V extends R {
16
20
  constructor() {
17
21
  super(...arguments);
18
22
  // Store is used for backward compatibility with product fetching and regeneration
19
- n(this, "store", R());
20
- n(this, "storeUnsubscription", () => {
23
+ a(this, "store", l());
24
+ a(this, "storeUnsubscription", () => {
21
25
  });
22
- n(this, "_debouncedRegenerateProductRows", m(() => {
26
+ a(this, "_debouncedRegenerateProductRows", L(() => {
23
27
  this._regenerateProductRows();
24
28
  }, 500));
25
29
  }
26
30
  getId() {
27
- return l;
31
+ return M;
28
32
  }
29
33
  getTemplate() {
30
34
  return `
31
35
  <div class="product-layout-control-container">
32
36
  ${this._GuTwoColumns([
33
37
  this._GuLabel({ text: "Number of Products" }),
34
- this._GuCounter({ name: o.PRODUCT_COUNT, maxValue: _ }),
35
- this._GuLabel({ text: "Products in One Row", name: o.PRODUCT_IN_ROW_LABEL }),
36
- this._GuCounter({ name: o.PRODUCT_IN_ROW, maxValue: h })
38
+ this._GuCounter({ name: e.PRODUCT_COUNT, maxValue: h }),
39
+ this._GuLabel({
40
+ text: "Products in One Row on Desktop",
41
+ name: e.PRODUCT_IN_ROW_LABEL
42
+ }),
43
+ this._GuCounter({ name: e.PRODUCT_IN_ROW, maxValue: p }),
44
+ this._GuLabel({
45
+ text: "Products in One Row on Mobile",
46
+ name: e.MOBILE_CARDS_IN_ROW_LABEL
47
+ }),
48
+ this._GuCounter({
49
+ name: e.MOBILE_CARDS_IN_ROW,
50
+ maxValue: g
51
+ })
37
52
  ])}
38
53
  </div>
39
54
  `;
40
55
  }
41
56
  onRender() {
42
- this._setFormValues(), this._updateProductsInRowVisibility(), this._listenToFormUpdates(), this._listenStateUpdates();
57
+ this._setFormValues(), this._updateProductsInRowVisibility(), this._listenToFormUpdates(), this._listenStateUpdates(), this._subscribeToEditorModeChanges();
43
58
  }
44
59
  onTemplateNodeUpdated(t) {
45
60
  super.onTemplateNodeUpdated(t), this._setFormValues(), this._updateProductsInRowVisibility();
@@ -48,48 +63,91 @@ class I extends c {
48
63
  this.storeUnsubscription();
49
64
  }
50
65
  _setFormValues() {
51
- const t = r.getConfig(this.currentNode);
66
+ const t = i.getConfig(this.currentNode);
52
67
  this.api.updateValues({
53
- [o.PRODUCT_COUNT]: t.size,
54
- [o.PRODUCT_IN_ROW]: t.cardsInRow
68
+ [e.PRODUCT_COUNT]: t.size,
69
+ [e.PRODUCT_IN_ROW]: t.cardsInRow,
70
+ [e.MOBILE_CARDS_IN_ROW]: t.mobileCardsInRow
55
71
  });
56
72
  }
57
73
  /**
58
- * Updates "Products in One Row" visibility based on layout orientation
59
- * This control is hidden for list layout (products always take full width)
60
- * Reads from node config first, falls back to DOM
74
+ * Checks if the editor is currently in mobile preview mode
75
+ * using Stripo's EditorStatePropertyType API.
76
+ */
77
+ _isMobileMode() {
78
+ return this.api.getEditorState()[c.previewDeviceMode] === C.MOBILE;
79
+ }
80
+ /**
81
+ * Updates counter visibility based on layout orientation and editor mode.
82
+ * - List layout: hide both counters (products always full-width)
83
+ * - Grid + desktop mode: show desktop counter, hide mobile counter
84
+ * - Grid + mobile mode: show mobile counter, hide desktop counter
61
85
  */
62
86
  _updateProductsInRowVisibility() {
63
- const u = (r.getConfig(this.currentNode).layout || C(this.currentNode)) === "grid";
64
- this.api.setVisibility(o.PRODUCT_IN_ROW, u), this.api.setVisibility(o.PRODUCT_IN_ROW_LABEL, u);
87
+ const o = (i.getConfig(this.currentNode).layout || N(this.currentNode)) === "grid", d = this._isMobileMode();
88
+ this.api.setVisibility(e.PRODUCT_IN_ROW, o && !d), this.api.setVisibility(e.PRODUCT_IN_ROW_LABEL, o && !d), this.api.setVisibility(e.MOBILE_CARDS_IN_ROW, o && d), this.api.setVisibility(e.MOBILE_CARDS_IN_ROW_LABEL, o && d);
89
+ }
90
+ /**
91
+ * Subscribes to editor preview mode changes via Stripo API.
92
+ * When the user switches between desktop/mobile preview, toggles
93
+ * which "Products in One Row" counter is visible.
94
+ */
95
+ _subscribeToEditorModeChanges() {
96
+ this.api.onEditorStatePropUpdated(
97
+ c.previewDeviceMode,
98
+ () => {
99
+ this._updateProductsInRowVisibility();
100
+ }
101
+ );
65
102
  }
66
103
  _onProductCountChange(t) {
67
- this.currentNode && (r.updateConfig(
104
+ !this.currentNode || i.getConfig(this.currentNode).size === t || (i.updateConfig(
68
105
  this.api,
69
106
  this.currentNode,
70
107
  { size: t },
71
108
  `Changed product count to ${t}`
72
- ), this.store.patchCurrentBlockConfig({ size: t }), this._debouncedRegenerateProductRows());
109
+ ), this.store.patchCurrentBlockConfig({ size: t }));
73
110
  }
74
111
  _onProductsInRowChange(t) {
75
- this.currentNode && (r.updateConfig(
112
+ if (!this.currentNode || i.getConfig(this.currentNode).cardsInRow === t)
113
+ return;
114
+ const o = t === 1 ? 0 : m;
115
+ i.updateConfig(
76
116
  this.api,
77
117
  this.currentNode,
78
- { cardsInRow: t },
118
+ { cardsInRow: t, columnSpacing: o },
79
119
  `Changed products per row to ${t}`
80
- ), this.store.patchCurrentBlockConfig({ cardsInRow: t }), this._debouncedRegenerateProductRows());
120
+ ), this.store.patchCurrentBlockConfig({ cardsInRow: t }, { triggerRefetch: !1 }), this._debouncedRegenerateProductRows();
81
121
  }
82
- _regenerateProductRows() {
83
- p({
122
+ _onMobileCardsInRowChange(t) {
123
+ if (!this.currentNode || i.getConfig(this.currentNode).mobileCardsInRow === t)
124
+ return;
125
+ const o = t === 1 ? 0 : O;
126
+ i.updateConfig(
127
+ this.api,
128
+ this.currentNode,
129
+ { mobileCardsInRow: t, mobileColumnSpacing: o },
130
+ `Changed mobile products per row to ${t}`
131
+ ), I(this.api), f({
84
132
  currentNode: this.currentNode,
85
133
  documentModifier: this.api.getDocumentModifier()
86
134
  });
87
135
  }
136
+ _regenerateProductRows() {
137
+ const t = i.getConfig(this.currentNode), s = parseInt(t.size) || 6, o = b(this.store.recommendationProducts, s);
138
+ P({
139
+ currentNode: this.currentNode,
140
+ documentModifier: this.api.getDocumentModifier(),
141
+ products: o
142
+ });
143
+ }
88
144
  _listenToFormUpdates() {
89
- this.api.onValueChanged(o.PRODUCT_COUNT, (t) => {
145
+ this.api.onValueChanged(e.PRODUCT_COUNT, (t) => {
90
146
  this._onProductCountChange(t.toString());
91
- }), this.api.onValueChanged(o.PRODUCT_IN_ROW, (t) => {
147
+ }), this.api.onValueChanged(e.PRODUCT_IN_ROW, (t) => {
92
148
  this._onProductsInRowChange(Number(t));
149
+ }), this.api.onValueChanged(e.MOBILE_CARDS_IN_ROW, (t) => {
150
+ this._onMobileCardsInRowChange(Number(t));
93
151
  });
94
152
  }
95
153
  /**
@@ -105,6 +163,6 @@ class I extends c {
105
163
  }
106
164
  }
107
165
  export {
108
- l as PRODUCT_LAYOUT_CONTROL_ID,
109
- I as ProductLayoutControl
166
+ M as PRODUCT_LAYOUT_CONTROL_ID,
167
+ V as ProductLayoutControl
110
168
  };