@useinsider/guido 2.1.0-beta.29e4650 → 2.1.0-beta.2d0ecb3

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 (98) 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/unsubscribe/UnsubscribePageSelection.vue.js +1 -1
  10. package/dist/components/organisms/unsubscribe/UnsubscribePageSelection.vue2.js +19 -19
  11. package/dist/composables/useBlocksConfig.js +26 -16
  12. package/dist/composables/useHtmlValidator.js +107 -119
  13. package/dist/composables/useRecommendation.js +9 -9
  14. package/dist/composables/useStripo.js +25 -23
  15. package/dist/composables/useVersionHistoryApi.js +1 -1
  16. package/dist/config/compiler/utils/recommendationCompilerUtils.js +28 -23
  17. package/dist/config/i18n/en/index.js +11 -0
  18. package/dist/config/i18n/en/labels.json.js +7 -0
  19. package/dist/config/i18n/en/toasters.json.js +56 -0
  20. package/dist/config/i18n/en/tooltips.json.js +82 -0
  21. package/dist/config/i18n/index.js +7 -0
  22. package/dist/config/migrator/itemsBlockMigrator.js +127 -122
  23. package/dist/config/migrator/recommendationMigrator.js +1 -1
  24. package/dist/enums/defaults.js +8 -4
  25. package/dist/extensions/Blocks/Recommendation/block.js +24 -23
  26. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +37 -33
  27. package/dist/extensions/Blocks/Recommendation/constants/layout.js +16 -12
  28. package/dist/extensions/Blocks/Recommendation/constants/selectors.js +15 -11
  29. package/dist/extensions/Blocks/Recommendation/controls/button/index.js +9 -9
  30. package/dist/extensions/Blocks/Recommendation/controls/image/index.js +1 -1
  31. package/dist/extensions/Blocks/Recommendation/controls/layout/index.js +41 -29
  32. package/dist/extensions/Blocks/Recommendation/controls/main/algorithm.js +16 -16
  33. package/dist/extensions/Blocks/Recommendation/controls/main/currency.js +30 -32
  34. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +203 -99
  35. package/dist/extensions/Blocks/Recommendation/controls/main/locale.js +9 -9
  36. package/dist/extensions/Blocks/Recommendation/controls/main/productLayout.js +97 -39
  37. package/dist/extensions/Blocks/Recommendation/controls/main/shuffle.js +16 -16
  38. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +304 -209
  39. package/dist/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.js +21 -0
  40. package/dist/extensions/Blocks/Recommendation/controls/name/index.js +10 -10
  41. package/dist/extensions/Blocks/Recommendation/controls/name/textTrim.js +5 -5
  42. package/dist/extensions/Blocks/Recommendation/controls/oldPrice/index.js +14 -14
  43. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/index.js +9 -9
  44. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textAfter.js +3 -3
  45. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textBefore.js +1 -1
  46. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/index.js +9 -9
  47. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textAfter.js +3 -3
  48. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textBefore.js +3 -3
  49. package/dist/extensions/Blocks/Recommendation/controls/price/index.js +3 -3
  50. package/dist/extensions/Blocks/Recommendation/controls/spacing/index.js +223 -99
  51. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +94 -55
  52. package/dist/extensions/Blocks/Recommendation/templates/grid/elementRenderer.js +25 -30
  53. package/dist/extensions/Blocks/Recommendation/templates/grid/migration.js +1 -1
  54. package/dist/extensions/Blocks/Recommendation/templates/grid/template.js +27 -30
  55. package/dist/extensions/Blocks/Recommendation/templates/list/elementRenderer.js +20 -25
  56. package/dist/extensions/Blocks/Recommendation/templates/utils.js +47 -31
  57. package/dist/extensions/Blocks/Unsubscribe/block.js +29 -29
  58. package/dist/extensions/Blocks/Unsubscribe/control.js +12 -9
  59. package/dist/extensions/Blocks/Unsubscribe/elements/preview.js +13 -11
  60. package/dist/extensions/Blocks/Unsubscribe/styles.css.js +31 -1
  61. package/dist/extensions/ModulesTabIcons/extension.js +17 -0
  62. package/dist/guido.css +1 -1
  63. package/dist/node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js +257 -187
  64. package/dist/services/stripoApi.js +20 -17
  65. package/dist/src/@types/config/schemas.d.ts +8 -0
  66. package/dist/src/composables/useConfig.d.ts +4 -0
  67. package/dist/src/config/i18n/en/index.d.ts +1 -0
  68. package/dist/src/config/i18n/index.d.ts +16 -0
  69. package/dist/src/enums/defaults.d.ts +4 -0
  70. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +6 -0
  71. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +3 -3
  72. package/dist/src/extensions/Blocks/Recommendation/constants/layout.d.ts +12 -2
  73. package/dist/src/extensions/Blocks/Recommendation/constants/selectors.d.ts +12 -1
  74. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +45 -10
  75. package/dist/src/extensions/Blocks/Recommendation/controls/main/productLayout.d.ts +22 -4
  76. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +36 -16
  77. package/dist/src/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.d.ts +29 -0
  78. package/dist/src/extensions/Blocks/Recommendation/controls/spacing/index.d.ts +50 -17
  79. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +1 -0
  80. package/dist/src/extensions/Blocks/Recommendation/templates/grid/migration.d.ts +1 -1
  81. package/dist/src/extensions/Blocks/Recommendation/templates/grid/template.d.ts +1 -1
  82. package/dist/src/extensions/Blocks/Recommendation/templates/index.d.ts +4 -0
  83. package/dist/src/extensions/Blocks/Recommendation/templates/list/migration.d.ts +1 -1
  84. package/dist/src/extensions/Blocks/Recommendation/templates/utils.d.ts +11 -2
  85. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +14 -0
  86. package/dist/src/extensions/Blocks/Unsubscribe/control.d.ts +1 -0
  87. package/dist/src/extensions/ModulesTabIcons/extension.d.ts +2 -0
  88. package/dist/src/stores/config.d.ts +36 -0
  89. package/dist/static/styles/components/notification.css.js +19 -0
  90. package/dist/static/styles/components/tools.css.js +6 -2
  91. package/dist/static/styles/components/version-history.css.js +10 -2
  92. package/dist/static/styles/components/wide-panel.css.js +18 -2
  93. package/dist/static/styles/customEditorStyle.css.js +59 -31
  94. package/dist/static/styles/variables.css.js +2 -0
  95. package/dist/static/templates/empty/index.html.js +74 -0
  96. package/dist/static/templates/empty/style.css.js +779 -0
  97. package/dist/stores/unsubscribe.js +37 -34
  98. package/package.json +1 -1
@@ -1,64 +1,77 @@
1
- var d = Object.defineProperty;
2
- var m = (c, s, t) => s in c ? d(c, s, { enumerable: !0, configurable: !0, writable: !0, value: t }) : c[s] = t;
3
- var n = (c, s, t) => m(c, typeof s != "symbol" ? s + "" : s, 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 b, getBlockElement as P, 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 nt, updateSingleProductContent as it } from "./utils.js";
20
- import { useDebounceFn as a } from "../../../../../node_modules/@vueuse/shared/index.js";
21
- const I = "recommendation-id", T = "ui-elements-recommendation-block";
22
- class B 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 b } from "./currency.js";
11
+ import { CURRENCY_CONTROL_ID as K } from "./currency.js";
12
+ import { FiltersControl as N } 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 S } from "./productLayout.js";
17
+ import { PRODUCT_LAYOUT_CONTROL_ID as tt } from "./productLayout.js";
18
+ import { ShuffleControl as I } from "./shuffle.js";
19
+ import { SHUFFLE_CONTROL_ID as ot } from "./shuffle.js";
20
+ import { getBlockElement as P, updateProductContentInPlace as L, regenerateProductRowsWithStyles as T } 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
- n(this, "store", C());
26
- n(this, "storeUnsubscription", () => {
27
+ i(this, "store", y());
28
+ i(this, "storeUnsubscription", () => {
27
29
  });
28
30
  // Sub-control instances for lifecycle management
29
- n(this, "algorithmControl", null);
30
- n(this, "localeControl", null);
31
- n(this, "currencyControl", null);
32
- n(this, "productLayoutControl", null);
33
- n(this, "filtersControl", null);
34
- n(this, "shuffleControl", null);
31
+ i(this, "algorithmControl", null);
32
+ i(this, "localeControl", null);
33
+ i(this, "currencyControl", null);
34
+ i(this, "productLayoutControl", null);
35
+ i(this, "filtersControl", null);
36
+ i(this, "shuffleControl", null);
35
37
  /**
36
38
  * Debounced product fetch to prevent rapid API calls during config changes
37
39
  */
38
- n(this, "_debouncedFetchProducts", a(() => {
40
+ i(this, "_debouncedFetchProducts", h(() => {
39
41
  this.store.fetchRecommendationProducts();
40
42
  }, 500));
41
43
  /**
42
- * Debounced regeneration when products arrive from API
43
- * 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.
44
53
  */
45
- n(this, "_debouncedRegenerateWithProducts", a(() => {
54
+ i(this, "_debouncedRegenerateWithProducts", h(() => {
46
55
  const t = this.store.recommendationProducts;
47
56
  if (!this.currentNode || !this.api)
48
57
  return;
49
58
  const e = this.api.getDocumentModifier();
50
- y({
59
+ L({
51
60
  currentNode: this.currentNode,
52
61
  documentModifier: e,
53
62
  products: t
54
- }) || this._regenerateWithProducts(t);
63
+ }) || T({
64
+ currentNode: this.currentNode,
65
+ documentModifier: e,
66
+ products: t
67
+ });
55
68
  }, 100));
56
69
  }
57
70
  getId() {
58
- return T;
71
+ return D;
59
72
  }
60
73
  getTemplate() {
61
- 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 b(), this.productLayoutControl = new S(), this.filtersControl = new N(), this.shuffleControl = new I(), `
62
75
  <div class="recommendation-controls-container">
63
76
  ${this.algorithmControl.getTemplate()}
64
77
  ${this.localeControl.getTemplate()}
@@ -78,26 +91,11 @@ class B extends h {
78
91
  }
79
92
  await this._fetchBlockData(t), this._initializeSubControls();
80
93
  }
81
- /**
82
- * Immediately regenerate products with styles (no debounce)
83
- * Used for initial load after fetch completes
84
- */
85
- _regenerateWithProducts(t) {
86
- if (!this.currentNode || !this.api) {
87
- console.warn("[Recommendation] Cannot regenerate - missing currentNode or api");
88
- return;
89
- }
90
- b({
91
- currentNode: this.currentNode,
92
- documentModifier: this.api.getDocumentModifier(),
93
- products: t
94
- });
95
- }
96
94
  onTemplateNodeUpdated(t) {
97
- var i;
95
+ var s;
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 && !((i = this.store.blockStates[e]) != null && i.isInitialized) && this._fetchBlockData(e), [
98
+ e !== null && e !== this.store.currentRecommendationId && this.store.setCurrentBlock(e), this._syncNodeConfigToStore(), e !== null && !((s = this.store.blockStates[e]) != null && s.isInitialized) && this._fetchBlockData(e), [
101
99
  this.algorithmControl,
102
100
  this.localeControl,
103
101
  this.currencyControl,
@@ -105,8 +103,8 @@ class B extends h {
105
103
  this.filtersControl,
106
104
  this.shuffleControl
107
105
  ].forEach((r) => {
108
- var l;
109
- r != null && r.api && (r.currentNode = t, (l = r.onTemplateNodeUpdated) == null || l.call(r, t));
106
+ var n;
107
+ r != null && r.api && (r.currentNode = t, (n = r.onTemplateNodeUpdated) == null || n.call(r, t));
110
108
  });
111
109
  }
112
110
  onDestroy() {
@@ -140,22 +138,115 @@ class B 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
+ const t = m.getConfig(this.currentNode);
153
+ this.store.patchCurrentBlockConfig({
154
+ strategy: t.strategy,
155
+ language: t.language,
156
+ size: t.size,
157
+ productIds: t.productIds,
158
+ filters: t.filters,
159
+ shuffleProducts: t.shuffleProducts,
160
+ currencySettings: {
161
+ name: t.currency.code,
162
+ value: t.currency.code,
163
+ symbol: t.currency.symbol,
164
+ alignment: t.currency.alignment === "before" ? "0" : "1",
165
+ decimalCount: t.currency.decimalCount.toString(),
166
+ decimalSeparator: t.currency.decimalSeparator,
167
+ thousandSeparator: t.currency.thousandSeparator
168
+ }
169
+ }, { triggerRefetch: !1 });
170
+ }
171
+ /**
172
+ * Fetches initial data for a block in three phases:
173
+ * 1. Shared reference data (algorithms, currencies, filters) — parallel
174
+ * 2. Smart defaults for new blocks (currency, algorithm) — sequential
175
+ * 3. Product data with correct defaults — sequential
176
+ *
144
177
  * Shared by onRender() and onTemplateNodeUpdated() to avoid duplication.
145
178
  * Marks the block as initialized to prevent redundant fetches on re-selection.
146
179
  */
147
180
  async _fetchBlockData(t) {
148
181
  t !== null && this.store.markBlockInitialized(t), (await Promise.allSettled([
149
- this.store.fetchRecommendationFilters(),
150
182
  this.store.fetchRecommendationCreateData(),
151
- this.store.fetchRecommendationProducts()
152
- ])).forEach((o, i) => {
153
- o.status === "rejected" && console.warn(`Recommendation block: ${[
154
- "fetchRecommendationFilters",
155
- "fetchRecommendationCreateData",
156
- "fetchRecommendationProducts"
157
- ][i]} failed`, o.reason);
158
- });
183
+ this.store.fetchRecommendationFilters()
184
+ ])).forEach((o, s) => {
185
+ o.status === "rejected" && console.warn(`Recommendation block: ${["fetchRecommendationCreateData", "fetchRecommendationFilters"][s]} failed`, o.reason);
186
+ }), this._applySmartDefaults();
187
+ try {
188
+ await this.store.fetchRecommendationProducts();
189
+ } catch (o) {
190
+ console.warn("Recommendation block: fetchRecommendationProducts failed", o);
191
+ }
192
+ }
193
+ /**
194
+ * Applies smart defaults for newly dropped blocks.
195
+ *
196
+ * For new blocks (config still matches hardcoded defaults), validates that
197
+ * the default currency and algorithm are available from the API response.
198
+ * If not, falls back to the first available option.
199
+ *
200
+ * Saved templates with user-customized config are left unchanged because
201
+ * their values won't match the hardcoded defaults.
202
+ */
203
+ _applySmartDefaults() {
204
+ if (!this.currentNode || !this.api)
205
+ return;
206
+ const t = m.getConfig(this.currentNode), e = {};
207
+ let o = null, s = null, r = null;
208
+ if (t.currency.code === a.currency.code) {
209
+ const { currencyList: n } = this.store;
210
+ n.length > 0 && (n.some(
211
+ (u) => u.value === `price.${a.currency.code}`
212
+ ) || (o = n[0].value.replace("price.", ""), e.currency = {
213
+ ...a.currency,
214
+ code: o,
215
+ symbol: o
216
+ }));
217
+ }
218
+ if (t.strategy === a.strategy) {
219
+ const n = this.store.getActivePredictiveAlgorithms;
220
+ n.length > 0 && (n.some(
221
+ (u) => u.value === a.strategy
222
+ ) || (s = n[0].value, e.strategy = s));
223
+ }
224
+ if (t.language === a.language) {
225
+ const n = this.store.getLanguages;
226
+ n.length > 0 && (n.some(
227
+ (u) => u.value === a.language
228
+ ) || (r = n[0].value, e.language = r));
229
+ }
230
+ !o && !s && !r || (m.updateConfig(
231
+ this.api,
232
+ this.currentNode,
233
+ e,
234
+ "Applied smart defaults"
235
+ ), this.store.patchCurrentBlockConfig({
236
+ ...o ? {
237
+ currencySettings: {
238
+ name: o,
239
+ value: o,
240
+ symbol: o,
241
+ alignment: a.currency.alignment === "before" ? "0" : "1",
242
+ decimalCount: a.currency.decimalCount.toString(),
243
+ decimalSeparator: a.currency.decimalSeparator,
244
+ thousandSeparator: a.currency.thousandSeparator
245
+ }
246
+ } : {},
247
+ ...s ? { strategy: s } : {},
248
+ ...r ? { language: r } : {}
249
+ }, { triggerRefetch: !1 }));
159
250
  }
160
251
  /**
161
252
  * Reads the recommendation-id attribute from the block element within the node
@@ -164,11 +255,11 @@ class B extends h {
164
255
  const e = P(t);
165
256
  if (!e || !("getAttribute" in e))
166
257
  return null;
167
- const o = e.getAttribute(I);
258
+ const o = e.getAttribute(k);
168
259
  if (!o)
169
260
  return null;
170
- const i = parseInt(o);
171
- return Number.isNaN(i) ? null : i;
261
+ const s = parseInt(o);
262
+ return Number.isNaN(s) ? null : s;
172
263
  }
173
264
  /**
174
265
  * Listen to store changes that require product refresh or regeneration.
@@ -176,42 +267,55 @@ class B extends h {
176
267
  * Uses configVersion counter (incremented only by patchCurrentBlockConfig)
177
268
  * to distinguish user-initiated config changes from internal mutations
178
269
  * (e.g., fetchRecommendationCreateData setting preferred currency).
270
+ *
271
+ * Tracks currentRecommendationId to detect block switches. When the user
272
+ * selects a different recommendation block, the proxy getters (e.g.,
273
+ * recommendationProducts) return the new block's data — a different array
274
+ * reference that would be falsely detected as "new products arrived".
275
+ * We skip that tick and update tracking references instead.
179
276
  */
180
277
  _listenStateUpdates() {
181
278
  const { store: t } = this;
182
- let e = t.recommendationProducts, o = t.$state.configVersion;
279
+ let e = t.recommendationProducts, o = t.$state.configVersion, s = t.currentRecommendationId;
183
280
  this.storeUnsubscription = t.$subscribe(() => {
184
- const i = t.$state.configVersion;
185
- i !== o && (o = i, this._debouncedFetchProducts());
186
- const r = t.recommendationProducts, l = r !== e, u = Array.isArray(r) && r.length > 0;
187
- l && u && (e = r, this._debouncedRegenerateWithProducts());
281
+ const r = t.currentRecommendationId;
282
+ if (r !== s) {
283
+ s = r, e = t.recommendationProducts, o = t.$state.configVersion;
284
+ return;
285
+ }
286
+ const n = t.$state.configVersion;
287
+ n !== o && (o = n, this._debouncedFetchProducts());
288
+ const l = t.recommendationProducts, u = l !== e, f = Array.isArray(l) && l.length > 0;
289
+ u && f && (e = l, this._debouncedRegenerateWithProducts());
188
290
  });
189
291
  }
190
292
  }
191
293
  export {
192
- M as ALGORITHM_CONTROL_ID,
193
- f as AlgorithmControl,
194
- T 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
- B as RecommendationBlockControl,
204
- J as SHUFFLE_CONTROL_ID,
205
- N as ShuffleControl,
206
- X as formatProductPrice,
294
+ H as ALGORITHM_CONTROL_ID,
295
+ R as AlgorithmControl,
296
+ D as CONTROL_BLOCK_ID,
297
+ K as CURRENCY_CONTROL_ID,
298
+ b as CurrencyControl,
299
+ J as FILTERS_CONTROL_ID,
300
+ N as FiltersControl,
301
+ X as LOCALE_CONTROL_ID,
302
+ _ as LocaleControl,
303
+ tt as PRODUCT_LAYOUT_CONTROL_ID,
304
+ S as ProductLayoutControl,
305
+ W as RecommendationBlockControl,
306
+ ot as SHUFFLE_CONTROL_ID,
307
+ I as ShuffleControl,
308
+ nt as adjustProductsToSize,
309
+ st as formatProductPrice,
207
310
  P as getBlockElement,
208
- Z as getCardComposition,
209
- tt as getCurrentLayout,
210
- et as reapplySpacing,
211
- ot as regenerateProductRows,
212
- b as regenerateProductRowsWithStyles,
213
- rt as setCurrencyAttributes,
214
- nt as updatePricesInPlace,
215
- y as updateProductContentInPlace,
216
- it as updateSingleProductContent
311
+ it as getCardComposition,
312
+ at as getCurrentLayout,
313
+ ct as reapplySpacing,
314
+ lt as regenerateMobileProductRows,
315
+ ut as regenerateProductRows,
316
+ T as regenerateProductRowsWithStyles,
317
+ dt as setCurrencyAttributes,
318
+ mt as updatePricesInPlace,
319
+ L as updateProductContentInPlace,
320
+ ht as updateSingleProductContent
217
321
  };
@@ -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
  };