@useinsider/guido 3.0.0 → 3.1.0

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 (101) hide show
  1. package/dist/components/organisms/header/EditorActions.vue.js +10 -8
  2. package/dist/components/organisms/header/EditorActions.vue2.js +41 -31
  3. package/dist/components/organisms/header/MigrationConfirmModal.vue.js +17 -0
  4. package/dist/components/organisms/header/MigrationConfirmModal.vue2.js +39 -0
  5. package/dist/components/organisms/onboarding/GenericOnboarding.vue.js +1 -1
  6. package/dist/components/organisms/onboarding/GenericOnboarding.vue2.js +1 -1
  7. package/dist/components/organisms/onboarding/TextBlockOnboarding.vue.js +1 -1
  8. package/dist/components/organisms/onboarding/TextBlockOnboarding.vue2.js +2 -2
  9. package/dist/config/compiler/unsubscribeCompilerRules.js +14 -14
  10. package/dist/config/compiler/utils/recommendationCompilerUtils.js +29 -18
  11. package/dist/config/i18n/en/labels.json.js +8 -3
  12. package/dist/config/migrator/itemsBlockMigrator.js +135 -131
  13. package/dist/config/migrator/recommendationMigrator.js +58 -54
  14. package/dist/enums/block.js +4 -0
  15. package/dist/extensions/Blocks/Items/block.js +30 -21
  16. package/dist/extensions/Blocks/Items/iconsRegistry.js +7 -6
  17. package/dist/extensions/Blocks/Items/items.css.js +48 -0
  18. package/dist/extensions/Blocks/Recommendation/block.js +64 -34
  19. package/dist/extensions/Blocks/Recommendation/constants/blockIds.js +1 -1
  20. package/dist/extensions/Blocks/Recommendation/constants/controlIds.js +1 -1
  21. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +36 -34
  22. package/dist/extensions/Blocks/Recommendation/constants/selectors.js +15 -12
  23. package/dist/extensions/Blocks/Recommendation/controls/cardBackground/index.js +4 -4
  24. package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +693 -144
  25. package/dist/extensions/Blocks/Recommendation/controls/customAttribute/index.js +78 -0
  26. package/dist/extensions/Blocks/Recommendation/controls/main/algorithm.js +15 -15
  27. package/dist/extensions/Blocks/Recommendation/controls/main/currency.js +24 -24
  28. package/dist/extensions/Blocks/Recommendation/controls/main/filters.js +2 -2
  29. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +107 -78
  30. package/dist/extensions/Blocks/Recommendation/controls/{layout/index.js → main/layoutOrientation.js} +34 -23
  31. package/dist/extensions/Blocks/Recommendation/controls/main/locale.js +2 -2
  32. package/dist/extensions/Blocks/Recommendation/controls/main/productCount.js +58 -0
  33. package/dist/extensions/Blocks/Recommendation/controls/main/productLayout.js +150 -64
  34. package/dist/extensions/Blocks/Recommendation/controls/main/shuffle.js +2 -2
  35. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +202 -200
  36. package/dist/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.js +25 -8
  37. package/dist/extensions/Blocks/Recommendation/controls/name/textTrim.js +6 -5
  38. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textAfter.js +8 -8
  39. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textBefore.js +21 -21
  40. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textAfter.js +13 -13
  41. package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textBefore.js +17 -17
  42. package/dist/extensions/Blocks/Recommendation/controls/spacing/index.js +94 -100
  43. package/dist/extensions/Blocks/Recommendation/controls/syncInfoMessage.js +65 -0
  44. package/dist/extensions/Blocks/Recommendation/extension.js +20 -18
  45. package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +5 -4
  46. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +209 -2
  47. package/dist/extensions/Blocks/Recommendation/settingsPanel.js +135 -111
  48. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +9 -7
  49. package/dist/extensions/Blocks/Recommendation/templates/grid/elementRenderer.js +63 -34
  50. package/dist/extensions/Blocks/Recommendation/templates/grid/template.js +25 -28
  51. package/dist/extensions/Blocks/Recommendation/templates/index.js +8 -8
  52. package/dist/extensions/Blocks/Recommendation/templates/list/elementRenderer.js +28 -13
  53. package/dist/extensions/Blocks/Recommendation/templates/list/template.js +25 -44
  54. package/dist/extensions/Blocks/Recommendation/templates/utils.js +62 -38
  55. package/dist/extensions/Blocks/common-control.js +96 -39
  56. package/dist/guido.css +1 -1
  57. package/dist/src/@types/extensions/block.d.ts +2 -0
  58. package/dist/src/App.vue.d.ts +3 -1
  59. package/dist/src/components/organisms/header/EditorActions.vue.d.ts +1 -1
  60. package/dist/src/components/organisms/header/MigrationConfirmModal.vue.d.ts +6 -0
  61. package/dist/src/components/wrappers/WpModal.vue.d.ts +2 -2
  62. package/dist/src/enums/block.d.ts +4 -0
  63. package/dist/src/extensions/Blocks/Items/block.d.ts +3 -1
  64. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +4 -1
  65. package/dist/src/extensions/Blocks/Recommendation/constants/blockIds.d.ts +2 -1
  66. package/dist/src/extensions/Blocks/Recommendation/constants/controlIds.d.ts +9 -1
  67. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  68. package/dist/src/extensions/Blocks/Recommendation/constants/selectors.d.ts +10 -0
  69. package/dist/src/extensions/Blocks/Recommendation/controls/cardComposition/index.d.ts +134 -44
  70. package/dist/src/extensions/Blocks/Recommendation/controls/customAttribute/index.d.ts +105 -0
  71. package/dist/src/extensions/Blocks/Recommendation/controls/index.d.ts +3 -2
  72. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +5 -1
  73. package/dist/src/extensions/Blocks/Recommendation/controls/{layout/index.d.ts → main/layoutOrientation.d.ts} +3 -3
  74. package/dist/src/extensions/Blocks/Recommendation/controls/main/productCount.d.ts +28 -0
  75. package/dist/src/extensions/Blocks/Recommendation/controls/main/productLayout.d.ts +38 -20
  76. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +6 -2
  77. package/dist/src/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.d.ts +23 -1
  78. package/dist/src/extensions/Blocks/Recommendation/controls/spacing/index.d.ts +8 -18
  79. package/dist/src/extensions/Blocks/Recommendation/controls/syncInfoMessage.d.ts +34 -0
  80. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +2 -0
  81. package/dist/src/extensions/Blocks/Recommendation/templates/grid/elementRenderer.d.ts +1 -1
  82. package/dist/src/extensions/Blocks/Recommendation/templates/list/elementRenderer.d.ts +1 -1
  83. package/dist/src/extensions/Blocks/Recommendation/templates/list/template.d.ts +10 -4
  84. package/dist/src/extensions/Blocks/Recommendation/templates/utils.d.ts +37 -2
  85. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +13 -0
  86. package/dist/src/extensions/Blocks/common-control.d.ts +29 -2
  87. package/dist/src/stores/template.d.ts +29 -0
  88. package/dist/src/utils/migrationBannerHtml.d.ts +2 -0
  89. package/dist/static/assets/info.svg.js +5 -0
  90. package/dist/static/styles/components/wide-panel.css.js +1 -0
  91. package/dist/static/styles/customEditorStyle.css.js +9 -0
  92. package/dist/static/styles/variables.css.js +3 -0
  93. package/dist/stores/template.js +15 -0
  94. package/dist/utils/migrationBannerHtml.js +21 -0
  95. package/package.json +3 -2
  96. package/dist/src/extensions/Blocks/Recommendation/controls/cardBackgroundColorControl.d.ts +0 -25
  97. package/dist/src/extensions/Blocks/Recommendation/controls/omnibusDiscountTextAfterControl.d.ts +0 -15
  98. package/dist/src/extensions/Blocks/Recommendation/controls/omnibusDiscountTextBeforeControl.d.ts +0 -15
  99. package/dist/src/extensions/Blocks/Recommendation/controls/omnibusPriceTextAfterControl.d.ts +0 -15
  100. package/dist/src/extensions/Blocks/Recommendation/controls/omnibusPriceTextBeforeControl.d.ts +0 -15
  101. package/dist/src/extensions/Blocks/Recommendation/controls/spacingControl.d.ts +0 -60
@@ -1,232 +1,781 @@
1
- var T = Object.defineProperty;
2
- var C = (a, s, t) => s in a ? T(a, s, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[s] = t;
3
- var l = (a, s, t) => C(a, typeof s != "symbol" ? s + "" : s, t);
4
- import { ModificationDescription as u } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
5
- import { CommonControl as f } from "../../../common-control.js";
6
- import { ATTR_PRODUCT_IMAGE as O, ATTR_PRODUCT_NAME as g, ATTR_PRODUCT_PRICE as R, ATTR_PRODUCT_OLD_PRICE as A, ATTR_PRODUCT_OMNIBUS_PRICE as D, ATTR_PRODUCT_OMNIBUS_DISCOUNT as N, ATTR_PRODUCT_BUTTON as V } from "../../constants/selectors.js";
7
- import { useRecommendationExtensionStore as E } from "../../store/recommendation.js";
8
- import { getTableDisplayValue as I } from "../../utils/tagName.js";
9
- import { getCurrentLayout as P } from "../main/utils.js";
10
- const S = "ui-elements-recommendation-card-composition", k = {
11
- ORDERABLE: "cardComposition"
12
- }, c = ".recommendation-attribute-row", v = ".product-card-wrapper > tbody", _ = "data-card-composition", d = "data-attribute-type", m = "data-visibility", n = [
13
- { key: O, label: "Product Image", visible: !0 },
14
- { key: g, label: "Product Name", visible: !0 },
15
- { key: R, label: "Product Price", visible: !0 },
16
- { key: A, label: "Product Original Price", visible: !0 },
17
- { key: D, label: "Omnibus Price", visible: !1 },
18
- { key: N, label: "Omnibus Discount", visible: !1 },
19
- { key: V, label: "Product Button", visible: !0 }
20
- ];
21
- class H extends f {
1
+ var X = Object.defineProperty;
2
+ var J = (p, f, t) => f in p ? X(p, f, { enumerable: !0, configurable: !0, writable: !0, value: t }) : p[f] = t;
3
+ var T = (p, f, t) => J(p, typeof f != "symbol" ? f + "" : f, t);
4
+ import { UIElementType as _, UEAttr as $, ModificationDescription as S } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
5
+ import { CommonControl as Q } from "../../../common-control.js";
6
+ import { ATTR_PRODUCT_IMAGE as L, ATTR_PRODUCT_NAME as Z, ATTR_PRODUCT_PRICE as tt, ATTR_PRODUCT_OLD_PRICE as et, ATTR_PRODUCT_OMNIBUS_PRICE as rt, ATTR_PRODUCT_OMNIBUS_DISCOUNT as ot, ATTR_PRODUCT_BUTTON as D, ATTR_DATA_CUSTOM_ATTRIBUTES as B, ATTR_CUSTOM_PREFIX as m } from "../../constants/selectors.js";
7
+ import { DEFAULT_COMPOSITION as U, DEFAULT_VISIBILITY as P } from "../../constants/defaultConfig.js";
8
+ import { RecommendationConfigService as st } from "../../services/configService.js";
9
+ import { useRecommendationExtensionStore as it } from "../../store/recommendation.js";
10
+ import { ATTRIBUTE_CELL_CLASS as nt, DEFAULT_CELL_PADDING as lt, gridElementRenderer as at } from "../../templates/grid/elementRenderer.js";
11
+ import { listElementRenderer as ct } from "../../templates/list/elementRenderer.js";
12
+ import { toDisplayName as ut, buildElementRenderer as w } from "../../templates/utils.js";
13
+ import { getTableDisplayValue as dt } from "../../utils/tagName.js";
14
+ import { getCurrentLayout as mt } from "../main/utils.js";
15
+ const ht = "ui-elements-recommendation-card-composition", A = ".recommendation-attribute-row", N = ".product-card-wrapper > tbody", F = ".product-info-cell > table > tbody", M = "data-card-composition", y = "data-attribute-type", I = "data-visibility", j = {
16
+ ADD_ATTRIBUTE: "addAttribute"
17
+ }, g = 5, v = "reorderIcon_", h = [
18
+ { key: L, label: "Product Image" },
19
+ { key: Z, label: "Product Name" },
20
+ { key: tt, label: "Product Price" },
21
+ { key: et, label: "Product Original Price" },
22
+ { key: rt, label: "Omnibus Price" },
23
+ { key: ot, label: "Omnibus Discount" },
24
+ { key: D, label: "Product Button" }
25
+ ], pt = new Set(h.map((p) => p.key)), C = "customAttr_", O = "deleteAttr_";
26
+ class Rt extends Q {
22
27
  constructor() {
23
28
  super(...arguments);
24
- l(this, "store", E());
25
- l(this, "unsubscribeOrientation", null);
29
+ T(this, "store", it());
30
+ T(this, "unsubscribeStore", null);
31
+ T(this, "eventController", null);
32
+ /**
33
+ * Guard flag: when true, onTemplateNodeUpdated skips _initializeComposition.
34
+ * Used during _onReorder to prevent multiple intermediate rebuilds.
35
+ */
36
+ T(this, "reorderInProgress", !1);
26
37
  }
27
38
  getId() {
28
- return S;
39
+ return ht;
29
40
  }
41
+ // ========================================================================
42
+ // Lifecycle
43
+ // ========================================================================
30
44
  getTemplate() {
31
- const t = n.map((e) => ({
32
- key: e.key,
33
- label: e.label,
34
- content: this._createItemContent(e.label, e.key)
35
- }));
45
+ const t = h.map((l) => `
46
+ <div data-toggle-key="${l.key}" style="display: none;">
47
+ ${this._GuToggle(`visibility_${l.key}`)}
48
+ </div>
49
+ `).join(""), r = Array.from(
50
+ { length: g },
51
+ (l, u) => `
52
+ <div data-custom-select-key="${C}${u}" style="display: none;">
53
+ ${this._GuSelect({
54
+ name: `${C}${u}`,
55
+ placeholder: this.api.translate("Select Attribute"),
56
+ options: []
57
+ })}
58
+ </div>
59
+ `
60
+ ).join(""), e = h.length + g, o = Array.from(
61
+ { length: e },
62
+ (l, u) => `
63
+ <div data-reorder-icon-key="${v}${u}" style="display: none;">
64
+ <${_.BUTTON}
65
+ class="drag-handle-btn flat-inline flat-white"
66
+ ${$.BUTTON.name}="${v}${u}"
67
+ >
68
+ <${_.ICON}
69
+ src="reorder"
70
+ class="icon-button"
71
+ ></${_.ICON}>
72
+ </${_.BUTTON}>
73
+ </div>
74
+ `
75
+ ).join(""), s = Array.from(
76
+ { length: g },
77
+ (l, u) => `
78
+ <div data-custom-delete-key="${O}${u}" style="display: none;">
79
+ <${_.BUTTON}
80
+ class="custom-attr-delete flat-inline flat-white"
81
+ ${$.BUTTON.name}="${O}${u}"
82
+ >
83
+ <${_.ICON}
84
+ src="delete"
85
+ class="icon-button"
86
+ ></${_.ICON}>
87
+ </${_.BUTTON}>
88
+ </div>
89
+ `
90
+ ).join(""), n = "https://academy.insiderone.com/docs/new-editor-email-recommendation-block", i = this.api.translate(
91
+ "Drag and drop the card elements to reorder them, adjust their visibility or add new attributes up to 5."
92
+ ), a = this.api.translate("For more information, you can"), c = this.api.translate("visit Academy");
36
93
  return `
37
- <div class="container" data-card-composition-control>
38
- ${this._GuLabel({ text: "Card Element Order & Visibility" })}
39
- ${this._GuOrderable(k.ORDERABLE, t)}
94
+ <div class="recommendation-controls-container" data-card-composition-control>
95
+ <div class="container">
96
+ <p class="card-composition-description">
97
+ ${i}
98
+ ${a}
99
+ <!-- cspell:disable-next-line -->
100
+ <a href="${n}" target="_blank" rel="noopener noreferrer">${c}</a>.
101
+ </p>
102
+ </div>
103
+
104
+ <div class="toggle-store" style="display: none;">
105
+ ${t}
106
+ </div>
107
+
108
+ <div class="custom-select-store" style="display: none;">
109
+ ${r}
110
+ </div>
111
+
112
+ <div class="custom-delete-store" style="display: none;">
113
+ ${s}
114
+ </div>
115
+
116
+ <div class="reorder-icon-store" style="display: none;">
117
+ ${o}
118
+ </div>
119
+
120
+ <div class="orderable-list" data-composition-list></div>
121
+
122
+ ${this._GuButton({
123
+ name: j.ADD_ATTRIBUTE,
124
+ label: this.api.translate("Add Attribute"),
125
+ id: "guido__btn-add-attribute"
126
+ })}
40
127
  </div>
41
128
  `;
42
129
  }
43
130
  onRender() {
44
- this._initializeComposition(), this._registerValueChangeListeners(), this._updateOrderableState(), this._subscribeToOrientationChanges();
131
+ this._initializeComposition(), this._registerValueChangeListeners(), this._setupEventListeners(), this._updateOrderableState(), this._subscribeToStoreChanges();
45
132
  }
46
133
  onTemplateNodeUpdated(t) {
47
- super.onTemplateNodeUpdated(t), this._initializeComposition(), this._updateOrderableState();
134
+ super.onTemplateNodeUpdated(t), !this.reorderInProgress && (this._initializeComposition(), this._updateOrderableState());
135
+ }
136
+ onDestroy() {
137
+ super.onDestroy(), this.eventController && (this.eventController.abort(), this.eventController = null), this.unsubscribeStore && (this.unsubscribeStore(), this.unsubscribeStore = null);
138
+ }
139
+ // ========================================================================
140
+ // Initialization
141
+ // ========================================================================
142
+ _initializeComposition() {
143
+ const t = this._readCompositionFromNode(), r = this._readCustomAttributesFromNode(), e = this._readVisibilityFromRows(), o = this._renderOrderableItems(t, r), s = this._buildVisibilityValues(e);
144
+ this.api.updateValues(s), o && this._initializeCustomSelects(r), this._updateAddButtonState();
145
+ }
146
+ _registerValueChangeListeners() {
147
+ h.forEach((t) => {
148
+ this.api.onValueChanged(`visibility_${t.key}`, (r) => {
149
+ this._applyVisibilityToBlock(t.key, r);
150
+ });
151
+ });
152
+ for (let t = 0; t < g; t++) {
153
+ const r = `${C}${t}`, e = t;
154
+ this.api.onValueChanged(r, (o) => {
155
+ this._onCustomAttributeChanged(e, o);
156
+ });
157
+ }
158
+ }
159
+ // ========================================================================
160
+ // State Reading
161
+ // ========================================================================
162
+ _readCompositionFromNode() {
163
+ if (!this.currentNode || !("getAttribute" in this.currentNode))
164
+ return [...U];
165
+ const t = this.currentNode.getAttribute(M);
166
+ return t ? t.split(",").filter(Boolean) : [...U];
167
+ }
168
+ _readCustomAttributesFromNode() {
169
+ if (!this.currentNode || !("getAttribute" in this.currentNode))
170
+ return [];
171
+ const t = this.currentNode.getAttribute(B);
172
+ if (!t)
173
+ return [];
174
+ try {
175
+ return JSON.parse(t);
176
+ } catch {
177
+ return [];
178
+ }
179
+ }
180
+ _readVisibilityFromRows() {
181
+ if (!this.currentNode)
182
+ return this._getDefaultVisibilities();
183
+ const t = Array.from(this.currentNode.querySelectorAll(A)), r = this._extractVisibilityFromRows(t);
184
+ return this._mergeWithDefaults(r);
185
+ }
186
+ _getDefaultVisibilities() {
187
+ return { ...P };
188
+ }
189
+ _extractVisibilityFromRows(t) {
190
+ const r = {};
191
+ return t.forEach((e) => {
192
+ if (!("getAttribute" in e))
193
+ return;
194
+ const o = e.getAttribute(y), s = e.getAttribute(I);
195
+ o && s !== null && (r[o] = this._parseVisibilityValue(s));
196
+ }), r;
197
+ }
198
+ _parseVisibilityValue(t) {
199
+ return t === "1" || t === "true";
200
+ }
201
+ _mergeWithDefaults(t) {
202
+ return Object.entries(P).forEach(([r, e]) => {
203
+ r in t || (t[r] = e);
204
+ }), t;
205
+ }
206
+ _buildVisibilityValues(t) {
207
+ return h.reduce((r, e) => (r[`visibility_${e.key}`] = t[e.key] ?? !0, r), {});
208
+ }
209
+ // ========================================================================
210
+ // UI Rendering (Orderable List)
211
+ // ========================================================================
212
+ /**
213
+ * Renders orderable items into the composition list.
214
+ * Returns `true` if a full DOM rebuild occurred, `false` if existing
215
+ * elements were reordered in-place (preserving UE-SELECT state).
216
+ */
217
+ _renderOrderableItems(t, r) {
218
+ const e = this._getControlContainer();
219
+ if (!e)
220
+ return !1;
221
+ const o = e.querySelector("[data-composition-list]");
222
+ if (!o || this._tryReorderInPlace(o, t))
223
+ return !1;
224
+ const s = new Set(r);
225
+ let n = 0, i = 0;
226
+ const a = t.map((c) => {
227
+ if (pt.has(c)) {
228
+ const l = h.find((u) => u.key === c);
229
+ return this._createBuiltInItemHtml(l, i++);
230
+ }
231
+ if (c.startsWith(m)) {
232
+ const l = c.substring(m.length);
233
+ if (s.has(l))
234
+ return this._createCustomItemHtml(c, n++, i++);
235
+ }
236
+ return "";
237
+ }).join("");
238
+ return this._rescueTogglesToStore(e), this._rescueSelectsToStore(e), this._rescueDeleteButtonsToStore(e), this._rescueReorderIconsToStore(e), o.innerHTML = a, this._moveTogglesIntoItems(e), this._moveSelectsIntoItems(e, r.length), this._moveDeleteButtonsIntoItems(e, r.length), this._moveReorderIconsIntoItems(e, i), !0;
48
239
  }
49
240
  /**
50
- * Creates the HTML content for an orderable item with label and toggle
241
+ * Attempts to reorder existing orderable-item elements to match the composition order.
242
+ * If the list already contains the same set of items (possibly in a different order),
243
+ * performs a lightweight DOM reorder instead of a full innerHTML rebuild.
244
+ * Returns true if reorder was performed, false if a full rebuild is needed.
51
245
  */
52
- _createItemContent(t, e) {
246
+ _tryReorderInPlace(t, r) {
247
+ const e = Array.from(t.querySelectorAll(".orderable-item"));
248
+ if (e.length !== r.length)
249
+ return !1;
250
+ const o = e.map((c) => c.dataset.key).filter(Boolean);
251
+ if (o.length !== r.length)
252
+ return !1;
253
+ const s = [...o].sort().join(","), n = [...r].sort().join(",");
254
+ if (s !== n || r.some((c) => c.startsWith(m)))
255
+ return !1;
256
+ const i = /* @__PURE__ */ new Map();
257
+ e.forEach((c) => {
258
+ const { key: l } = c.dataset;
259
+ if (l) {
260
+ const u = i.get(l) || [];
261
+ u.push(c), i.set(l, u);
262
+ }
263
+ });
264
+ const a = /* @__PURE__ */ new Map();
265
+ return r.forEach((c) => {
266
+ const l = i.get(c);
267
+ if (!l)
268
+ return;
269
+ const u = a.get(c) || 0;
270
+ a.set(c, u + 1), l[u] && t.appendChild(l[u]);
271
+ }), !0;
272
+ }
273
+ _createBuiltInItemHtml(t, r) {
274
+ return `
275
+ <div class="orderable-item" draggable="true" data-key="${t.key}">
276
+ <span class="drag-handle" data-reorder-icon-slot="${r}"></span>
277
+ <span class="item-label">${this.api.translate(t.label)}</span>
278
+ <div class="item-action" data-action-for="${t.key}"></div>
279
+ </div>
280
+ `;
281
+ }
282
+ _createCustomItemHtml(t, r, e) {
53
283
  return `
54
- <div style="display: flex; align-items: center; justify-content: space-between;
55
- padding: 8px; gap: 8px;">
56
- <span style="flex: 1;">${t}</span>
57
- ${this._GuToggle(`visibility_${e}`)}
284
+ <div class="orderable-item" draggable="true"
285
+ data-key="${t}" data-custom-index="${r}">
286
+ <span class="drag-handle" data-reorder-icon-slot="${e}"></span>
287
+ <div class="custom-attr-select-wrap"
288
+ data-custom-select-slot="${r}"></div>
289
+ <div class="item-action"
290
+ data-custom-index="${r}"
291
+ data-custom-delete-slot="${r}"></div>
58
292
  </div>
59
293
  `;
60
294
  }
61
295
  /**
62
- * Registers event listeners for composition and visibility changes
296
+ * Builds select options from the store's filterList.
297
+ * Falls back to a single option for the currently selected attribute.
63
298
  */
64
- _registerValueChangeListeners() {
65
- this.api.onValueChanged("cardComposition", (t) => {
66
- this._applyCompositionToBlock(t);
67
- }), n.forEach((t) => {
68
- this.api.onValueChanged(`visibility_${t.key}`, (e) => {
69
- this._applyVisibilityToBlock(t.key, e);
299
+ _getSelectOptions(t, r = []) {
300
+ const { filterList: e } = this.store, o = Object.values(e);
301
+ if (o.length > 0) {
302
+ const n = new Set(r);
303
+ return n.delete(t), o.filter((i) => !n.has(i.attributeName)).map((i) => ({
304
+ text: i.displayName,
305
+ value: i.attributeName
306
+ }));
307
+ }
308
+ return [{ text: this._getDisplayNameForAttribute(t), value: t }];
309
+ }
310
+ /**
311
+ * Initializes custom attribute selects: sets their options and current value.
312
+ * Selects are pre-allocated in getTemplate() and moved into items by
313
+ * _moveSelectsIntoItems — this method only updates their data.
314
+ */
315
+ _initializeCustomSelects(t) {
316
+ t.length !== 0 && setTimeout(() => {
317
+ t.forEach((r, e) => {
318
+ const o = `${C}${e}`, s = this._getSelectOptions(r, t);
319
+ this.api.setUIEAttribute(o, $.SELECTPICKER.items, s), this.api.updateValues({ [o]: r });
70
320
  });
321
+ }, 0);
322
+ }
323
+ /**
324
+ * Moves UE-SWITCHER elements from the hidden toggle store into orderable items.
325
+ * Stripo initializes toggles at template parse time; moving the DOM node preserves bindings.
326
+ */
327
+ _moveTogglesIntoItems(t) {
328
+ h.forEach((r) => {
329
+ const e = t.querySelector(`[data-toggle-key="${r.key}"]`), o = t.querySelector(`[data-action-for="${r.key}"]`);
330
+ if (e && o) {
331
+ const s = e.querySelector("ue-switcher");
332
+ s && o.appendChild(s);
333
+ }
71
334
  });
72
335
  }
73
336
  /**
74
- * Initializes composition order and visibility state from the current node
337
+ * Moves pre-allocated UE-SELECT elements from the hidden custom-select-store
338
+ * into orderable item slots. Same pattern as _moveTogglesIntoItems.
75
339
  */
76
- _initializeComposition() {
77
- const t = this._readCompositionFromNode(), e = this._readVisibilityFromRows(), i = {
78
- cardComposition: t,
79
- ...this._buildVisibilityValues(e)
80
- };
81
- this.api.updateValues(i);
340
+ _moveSelectsIntoItems(t, r) {
341
+ for (let e = 0; e < r; e++) {
342
+ const o = `${C}${e}`, s = t.querySelector(`[data-custom-select-key="${o}"]`), n = t.querySelector(`[data-custom-select-slot="${e}"]`);
343
+ if (s && n) {
344
+ const i = s.querySelector("ue-select");
345
+ i && n.appendChild(i);
346
+ }
347
+ }
82
348
  }
83
349
  /**
84
- * Reads composition order from node's data-card-composition attribute
85
- * Falls back to default order if attribute is not present
350
+ * Rescues UE-SWITCHER elements from orderable items back to the hidden toggle store.
351
+ * Must be called before re-rendering the list with innerHTML to prevent toggle destruction.
352
+ * Without this, onTemplateNodeUpdated → _renderOrderableItems → innerHTML would destroy
353
+ * previously-moved toggles, making them permanently lost.
86
354
  */
87
- _readCompositionFromNode() {
88
- if (!this.currentNode || !("getAttribute" in this.currentNode))
89
- return n.map((e) => e.key);
90
- const t = this.currentNode.getAttribute(_);
91
- return t ? t.split(",").filter(Boolean) : n.map((e) => e.key);
355
+ _rescueTogglesToStore(t) {
356
+ h.forEach((r) => {
357
+ const e = t.querySelector(`[data-toggle-key="${r.key}"]`), o = t.querySelector(`[data-action-for="${r.key}"]`);
358
+ if (o) {
359
+ const s = o.querySelector("ue-switcher");
360
+ s && e && e.appendChild(s);
361
+ }
362
+ });
92
363
  }
93
364
  /**
94
- * Builds visibility values object from the visibility map
365
+ * Rescues UE-SELECT elements from orderable items back to the hidden custom-select-store.
366
+ * Same rescue pattern as _rescueTogglesToStore — prevents innerHTML from destroying them.
95
367
  */
96
- _buildVisibilityValues(t) {
97
- return n.reduce((e, i) => (e[`visibility_${i.key}`] = t[i.key] ?? !0, e), {});
368
+ _rescueSelectsToStore(t) {
369
+ for (let r = 0; r < g; r++) {
370
+ const e = `${C}${r}`, o = t.querySelector(`[data-custom-select-key="${e}"]`), s = t.querySelector(`[data-custom-select-slot="${r}"]`);
371
+ if (s) {
372
+ const n = s.querySelector("ue-select");
373
+ n && o && o.appendChild(n);
374
+ }
375
+ }
98
376
  }
99
377
  /**
100
- * Read visibility state from individual row elements' data-visibility attributes
101
- * This ensures toggles reflect the actual DOM state
378
+ * Moves pre-allocated UE-BUTTON delete elements from the hidden custom-delete-store
379
+ * into orderable item slots. Same pattern as _moveSelectsIntoItems.
102
380
  */
103
- _readVisibilityFromRows() {
104
- if (!this.currentNode)
105
- return this._getDefaultVisibilities();
106
- const t = Array.from(this.currentNode.querySelectorAll(c)), e = this._extractVisibilityFromRows(t);
107
- return this._mergeWithDefaults(e);
381
+ _moveDeleteButtonsIntoItems(t, r) {
382
+ for (let e = 0; e < r; e++) {
383
+ const o = `${O}${e}`, s = t.querySelector(`[data-custom-delete-key="${o}"]`), n = t.querySelector(`[data-custom-delete-slot="${e}"]`);
384
+ if (s && n) {
385
+ const i = s.querySelector("ue-button");
386
+ i && n.appendChild(i);
387
+ }
388
+ }
108
389
  }
109
390
  /**
110
- * Returns default visibility values for all items
391
+ * Rescues UE-BUTTON delete elements from orderable items back to the hidden custom-delete-store.
392
+ * Same rescue pattern as _rescueSelectsToStore — prevents innerHTML from destroying them.
111
393
  */
112
- _getDefaultVisibilities() {
113
- return n.reduce((t, e) => (t[e.key] = e.visible, t), {});
394
+ _rescueDeleteButtonsToStore(t) {
395
+ for (let r = 0; r < g; r++) {
396
+ const e = `${O}${r}`, o = t.querySelector(`[data-custom-delete-key="${e}"]`), s = t.querySelector(`[data-custom-delete-slot="${r}"]`);
397
+ if (s) {
398
+ const n = s.querySelector("ue-button");
399
+ n && o && o.appendChild(n);
400
+ }
401
+ }
114
402
  }
115
403
  /**
116
- * Extracts visibility values from DOM nodes
404
+ * Moves pre-allocated reorder icon UE-BUTTON elements from the hidden reorder-icon-store
405
+ * into orderable item drag-handle slots. Same pattern as _moveDeleteButtonsIntoItems.
117
406
  */
118
- _extractVisibilityFromRows(t) {
119
- const e = {};
120
- return t.forEach((i) => {
121
- if (!("getAttribute" in i))
407
+ _moveReorderIconsIntoItems(t, r) {
408
+ for (let e = 0; e < r; e++) {
409
+ const o = `${v}${e}`, s = t.querySelector(`[data-reorder-icon-key="${o}"]`), n = t.querySelector(`[data-reorder-icon-slot="${e}"]`);
410
+ if (s && n) {
411
+ const i = s.querySelector("ue-button");
412
+ i && n.appendChild(i);
413
+ }
414
+ }
415
+ }
416
+ /**
417
+ * Rescues reorder icon UE-BUTTON elements from orderable items back to the hidden reorder-icon-store.
418
+ * Same rescue pattern as _rescueDeleteButtonsToStore — prevents innerHTML from destroying them.
419
+ */
420
+ _rescueReorderIconsToStore(t) {
421
+ const r = h.length + g;
422
+ for (let e = 0; e < r; e++) {
423
+ const o = `${v}${e}`, s = t.querySelector(`[data-reorder-icon-key="${o}"]`), n = t.querySelector(`[data-reorder-icon-slot="${e}"]`);
424
+ if (n) {
425
+ const i = n.querySelector("ue-button");
426
+ i && s && s.appendChild(i);
427
+ }
428
+ }
429
+ }
430
+ // ========================================================================
431
+ // Event Listeners (Drag & Drop, Delete, Add Attribute)
432
+ // ========================================================================
433
+ _setupEventListeners() {
434
+ this.eventController && this.eventController.abort(), this.eventController = new AbortController();
435
+ const { signal: t } = this.eventController, r = this._getControlContainer();
436
+ if (!r)
437
+ return;
438
+ const e = r.querySelector("[data-composition-list]"), o = r.querySelector("#guido__btn-add-attribute");
439
+ e && (this._setupDragAndDrop(e, t), this._setupDeleteHandler(e, t)), o && o.addEventListener("click", () => {
440
+ const s = new Set(this._readCustomAttributesFromNode()), i = Object.values(this.store.filterList).find((a) => !s.has(a.attributeName));
441
+ i && this._onAddAttribute(i.attributeName, i.displayName);
442
+ }, { signal: t });
443
+ }
444
+ _setupDragAndDrop(t, r) {
445
+ let e = null, o = null;
446
+ t.addEventListener("dragstart", (s) => {
447
+ var a;
448
+ const i = s.target.closest(".orderable-item");
449
+ i && (e = i, i.classList.add("dragging"), (a = s.dataTransfer) == null || a.setData("text/plain", i.dataset.key || ""));
450
+ }, { signal: r }), t.addEventListener("dragend", () => {
451
+ e && e.classList.remove("dragging"), e = null, o == null || o.classList.remove("drag-over"), o = null;
452
+ }, { signal: r }), t.addEventListener("dragover", (s) => {
453
+ s.preventDefault();
454
+ const i = s.target.closest(".orderable-item"), a = i && i !== e ? i : null;
455
+ a !== o && (o == null || o.classList.remove("drag-over"), o = a, o == null || o.classList.add("drag-over"));
456
+ }, { signal: r }), t.addEventListener("drop", (s) => {
457
+ s.preventDefault();
458
+ const i = s.target.closest(".orderable-item");
459
+ if (!i || !e || i === e)
122
460
  return;
123
- const o = i.getAttribute(d), r = i.getAttribute(m);
124
- o && r !== null && (e[o] = this._parseVisibilityValue(r));
125
- }), e;
461
+ const a = i.getBoundingClientRect(), c = a.top + a.height / 2;
462
+ s.clientY < c ? t.insertBefore(e, i) : t.insertBefore(e, i.nextSibling), o == null || o.classList.remove("drag-over"), o = null, e.classList.remove("dragging");
463
+ const u = t.querySelectorAll(".orderable-item"), d = Array.from(u).map((b) => b.dataset.key).filter(Boolean);
464
+ this._onReorder(d), e = null;
465
+ }, { signal: r });
466
+ }
467
+ _setupDeleteHandler(t, r) {
468
+ t.addEventListener("click", (e) => {
469
+ const s = e.target.closest(".custom-attr-delete");
470
+ if (!s)
471
+ return;
472
+ const n = s.closest("[data-custom-index]"), i = n == null ? void 0 : n.dataset.customIndex;
473
+ i !== void 0 && this._onDeleteCustomAttribute(Number(i));
474
+ }, { signal: r });
475
+ }
476
+ // ========================================================================
477
+ // Actions (Add, Delete, Reorder)
478
+ // ========================================================================
479
+ _onAddAttribute(t, r) {
480
+ const e = `${m}${t}`, o = this._readCompositionFromNode();
481
+ o.push(e);
482
+ const s = [...this._readCustomAttributesFromNode(), t];
483
+ this._updateBothAttributes(o, s), this._injectCustomAttributeHtml(t, r, e, o), this._renderOrderableItems(o, s), this._initializeCustomSelects(s), this._updateAddButtonState();
126
484
  }
127
485
  /**
128
- * Parses visibility value from string to boolean
129
- * Accepts "1", "true" as true, everything else as false
486
+ * Removes a single custom attribute by its index in the customAttrs array.
487
+ * Index-based to correctly handle duplicate attributes.
130
488
  */
131
- _parseVisibilityValue(t) {
132
- return t === "1" || t === "true";
489
+ _onDeleteCustomAttribute(t) {
490
+ const r = this._readCustomAttributesFromNode();
491
+ if (r[t] === void 0)
492
+ return;
493
+ const o = this._readCompositionFromNode(), s = this._findNthCustomKeyIndex(o, t), n = o.filter((a, c) => c !== s), i = r.filter((a, c) => c !== t);
494
+ this._updateBothAttributes(n, i), this._removeCustomAttributeHtml(n), this._renderOrderableItems(n, i), this._initializeCustomSelects(i), this._updateAddButtonState();
133
495
  }
134
496
  /**
135
- * Merges extracted visibilities with default values for missing keys
497
+ * Handles changing a custom attribute's selection via its inline _GuSelect.
498
+ * Uses the customIndex to target only the specific instance, supporting duplicates.
136
499
  */
137
- _mergeWithDefaults(t) {
138
- return n.forEach((e) => {
139
- e.key in t || (t[e.key] = e.visible);
140
- }), t;
500
+ _onCustomAttributeChanged(t, r) {
501
+ const e = this._readCustomAttributesFromNode(), o = e[t];
502
+ if (o === void 0 || o === r)
503
+ return;
504
+ const s = `${m}${r}`, n = this._readCompositionFromNode(), i = this._findNthCustomKeyIndex(n, t);
505
+ i !== -1 && (n[i] = s), e[t] = r;
506
+ const a = this._getDisplayNameForAttribute(r);
507
+ this._updateBothAttributes(n, e), this._injectCustomAttributeHtml(r, a, s, n), this._renderOrderableItems(n, e), this._initializeCustomSelects(e);
508
+ }
509
+ _onReorder(t) {
510
+ const r = t.filter((e) => e.startsWith(m)).map((e) => e.substring(m.length));
511
+ this.reorderInProgress = !0;
512
+ try {
513
+ this._updateBothAttributes(t, r), this._getCurrentLayout() === "grid" && this._reorderProductAttributes(t);
514
+ } finally {
515
+ this.reorderInProgress = !1;
516
+ }
517
+ this._initializeComposition(), this._updateOrderableState();
518
+ }
519
+ // ========================================================================
520
+ // HTML Injection / Removal (Product Card DOM)
521
+ // ========================================================================
522
+ _injectCustomAttributeHtml(t, r, e, o) {
523
+ if (!this.currentNode)
524
+ return;
525
+ this._getCurrentLayout() === "grid" ? this._injectGridAttributeRow(t, r, e, o) : this._injectListAttributeRow(t, r, e, o);
526
+ }
527
+ _injectGridAttributeRow(t, r, e, o) {
528
+ const s = this.currentNode.querySelectorAll(N);
529
+ if (!(s != null && s.length))
530
+ return;
531
+ const n = st.getConfig(this.currentNode), a = `0 ${Math.floor(n.columnSpacing / 2)}px`, c = this.api.getDocumentModifier(), l = this.store.recommendationProducts.length;
532
+ let u = 0;
533
+ s.forEach((d) => {
534
+ var H;
535
+ const b = d.querySelector(A), R = ((H = b == null ? void 0 : b.querySelectorAll(`.${nt}`)) == null ? void 0 : H.length) || 1, x = (100 / R).toFixed(2), { bgStyle: V, bgAttr: W } = this._extractSegmentBgFromCard(d), K = o.map((k) => {
536
+ if (k === e) {
537
+ const z = Array.from(
538
+ { length: R },
539
+ (ft, q) => {
540
+ const G = l > 0 ? (u + q) % l : q, Y = this._resolveAttributeContent(t, r, G);
541
+ return this._getGridCellHtml(
542
+ t,
543
+ Y,
544
+ x,
545
+ V,
546
+ W,
547
+ a
548
+ );
549
+ }
550
+ ).join("");
551
+ return `<tr class="recommendation-attribute-row" ${y}="${e}" ${I}="1">${z}</tr>`;
552
+ }
553
+ const E = d.querySelector(
554
+ `${A}[${y}="${k}"]`
555
+ );
556
+ return E && "getOuterHTML" in E ? E.getOuterHTML() : "";
557
+ }).join("");
558
+ u += R, l > 0 && u >= l && (u = 0), c.modifyHtml(d).setInnerHtml(K);
559
+ }), c.apply(new S(`${this.api.translate("Add custom attribute")}: ${r}`));
560
+ }
561
+ _injectListAttributeRow(t, r, e, o) {
562
+ const s = this.currentNode.querySelectorAll(F);
563
+ if (!(s != null && s.length))
564
+ return;
565
+ const n = o.filter((a) => a !== L && a !== D), i = this.api.getDocumentModifier();
566
+ s.forEach((a, c) => {
567
+ const l = n.map((u) => {
568
+ if (u === e) {
569
+ const b = this._resolveAttributeContent(t, r, c);
570
+ return this._getListRowHtml(t, b, e);
571
+ }
572
+ const d = a.querySelector(
573
+ `${A}[${y}="${u}"]`
574
+ );
575
+ return d && "getOuterHTML" in d ? d.getOuterHTML() : "";
576
+ }).join("");
577
+ i.modifyHtml(a).setInnerHtml(l);
578
+ }), i.apply(new S(`${this.api.translate("Add custom attribute")}: ${r}`));
141
579
  }
142
580
  /**
143
- * Apply the reordered composition to the block's HTML structure
144
- * Updates the data-card-composition attribute and reorders product attributes
145
- * Note: Reordering is only applied for grid layout
581
+ * Removes a custom attribute by rebuilding product card content without it.
582
+ * The composition parameter should already have the deleted key removed.
146
583
  */
147
- _applyCompositionToBlock(t) {
584
+ _removeCustomAttributeHtml(t) {
148
585
  if (!this.currentNode)
149
586
  return;
150
- const e = this._getCurrentLayout();
151
- this.api.getDocumentModifier().modifyHtml(this.currentNode).setAttribute(_, t.join(",")).apply(new u("Update card composition")), e === "grid" && this._reorderProductAttributes(t);
587
+ const r = this._getCurrentLayout(), e = this.api.getDocumentModifier();
588
+ if (r === "grid") {
589
+ const o = this.currentNode.querySelectorAll(N);
590
+ o == null || o.forEach((s) => {
591
+ const n = this._buildCompositionHtml(s, t);
592
+ e.modifyHtml(s).setInnerHtml(n);
593
+ });
594
+ } else {
595
+ const o = t.filter((n) => n !== L && n !== D), s = this.currentNode.querySelectorAll(F);
596
+ s == null || s.forEach((n) => {
597
+ const i = this._buildCompositionHtml(n, o);
598
+ e.modifyHtml(n).setInnerHtml(i);
599
+ });
600
+ }
601
+ e.apply(new S(this.api.translate("Remove custom attribute")));
152
602
  }
603
+ // ========================================================================
604
+ // DOM Mutation (Block Root Attributes, Reorder)
605
+ // ========================================================================
153
606
  /**
154
- * Reorders attribute rows within each product card based on composition order
155
- * Targets the tbody inside each product-attribute-cell to preserve card structure
607
+ * Atomically updates both composition and custom-attributes in a single apply()
608
+ * so that onTemplateNodeUpdated fires only once with fully consistent state.
609
+ * Two separate apply() calls would cause an intermediate onTemplateNodeUpdated
610
+ * where composition is updated but customAttributes still has the old order,
611
+ * producing a flicker on the custom attribute dropdowns.
612
+ */
613
+ _updateBothAttributes(t, r) {
614
+ this.currentNode && this.api.getDocumentModifier().modifyHtml(this.currentNode).setAttribute(M, t.join(",")).setAttribute(B, JSON.stringify(r)).apply(new S(this.api.translate("Update card composition")));
615
+ }
616
+ /**
617
+ * Reorders attribute rows within each product card based on composition order.
618
+ * Only used for grid layout (list layout has fixed 3-column structure).
156
619
  */
157
620
  _reorderProductAttributes(t) {
158
621
  if (!this.currentNode)
159
622
  return;
160
- const e = this.currentNode.querySelectorAll(v);
161
- if (!(e != null && e.length))
623
+ const r = this.currentNode.querySelectorAll(N);
624
+ if (!(r != null && r.length))
162
625
  return;
163
- const i = this.api.getDocumentModifier();
164
- e.forEach((o) => {
165
- const r = this._buildCompositionHtml(o, t);
166
- i.modifyHtml(o).setInnerHtml(r);
167
- }), i.apply(new u("Reorder product attributes"));
626
+ const e = this.api.getDocumentModifier();
627
+ r.forEach((o) => {
628
+ const s = this._buildCompositionHtml(o, t);
629
+ e.modifyHtml(o).setInnerHtml(s);
630
+ }), e.apply(new S(this.api.translate("Reorder product attributes")));
168
631
  }
169
632
  /**
170
- * Builds HTML string with attributes ordered according to composition
633
+ * Builds HTML string with attributes ordered according to composition.
634
+ * Queries existing rows from the container by data-attribute-type.
171
635
  */
172
- _buildCompositionHtml(t, e) {
173
- return e.reduce((i, o) => {
174
- const r = t.querySelector(`${c}[${d}="${o}"]`);
175
- return r && "getOuterHTML" in r ? i + r.getOuterHTML() : i;
636
+ _buildCompositionHtml(t, r) {
637
+ return r.reduce((e, o) => {
638
+ const s = t.querySelector(`${A}[${y}="${o}"]`);
639
+ return s && "getOuterHTML" in s ? e + s.getOuterHTML() : e;
176
640
  }, "");
177
641
  }
178
- /**
179
- * Apply visibility changes to the block's HTML structure
180
- * Updates display style and data-visibility attribute for all matching elements
181
- * - <tr> elements: use display: none / table-row
182
- * - <td> elements: use display: none / table-cell
183
- */
184
- _applyVisibilityToBlock(t, e) {
642
+ // ========================================================================
643
+ // Visibility
644
+ // ========================================================================
645
+ _applyVisibilityToBlock(t, r) {
185
646
  if (!this.currentNode)
186
647
  return;
187
- const i = this.currentNode.querySelectorAll(`${c}[${d}="${t}"]`);
188
- if (!(i != null && i.length))
648
+ const e = this.currentNode.querySelectorAll(`${A}[${y}="${t}"]`);
649
+ if (!(e != null && e.length))
189
650
  return;
190
- const o = e ? "1" : "0", r = `Set ${t} visibility to ${e ? "visible" : "hidden"}`, b = this.api.getDocumentModifier();
191
- i.forEach((p) => {
192
- const y = I(p), h = e ? y : "none";
193
- b.modifyHtml(p).setStyle("display", h).setAttribute(m, o);
194
- }), b.apply(new u(r));
651
+ const o = r ? "1" : "0", s = r ? this.api.translate("visible") : this.api.translate("hidden"), n = `${this.api.translate("Set visibility")}: ${t} → ${s}`, i = this.api.getDocumentModifier();
652
+ e.forEach((a) => {
653
+ const c = dt(a), l = r ? c : "none";
654
+ i.modifyHtml(a).setStyle("display", l).setAttribute(I, o);
655
+ }), i.apply(new S(n));
195
656
  }
657
+ // ========================================================================
658
+ // Utilities
659
+ // ========================================================================
196
660
  /**
197
- * Gets the current layout orientation from store or DOM
661
+ * Finds the composition array index for the Nth custom attribute (0-based).
662
+ * Scans left-to-right, counting only entries with the custom prefix.
663
+ * The customIndex maps 1:1 with the customAttrs array order.
198
664
  */
665
+ _findNthCustomKeyIndex(t, r) {
666
+ let e = 0;
667
+ for (let o = 0; o < t.length; o++)
668
+ if (t[o].startsWith(m)) {
669
+ if (e === r)
670
+ return o;
671
+ e++;
672
+ }
673
+ return -1;
674
+ }
199
675
  _getCurrentLayout() {
200
- return this.store.recommendationConfigs.orientation || P(this.currentNode);
676
+ return this.store.recommendationConfigs.orientation || mt(this.currentNode);
677
+ }
678
+ /**
679
+ * Extracts background color properties from existing card elements.
680
+ * Checks both `.product-card-segment` (where applyCardBackgroundColor sets bg for grid)
681
+ * and `.product-card-wrapper` (where CardBackgroundColorControl sets bg).
682
+ * Used when injecting new attribute cells to match the card's current background.
683
+ */
684
+ _extractSegmentBgFromCard(t) {
685
+ var o;
686
+ const r = t.querySelector(".product-card-segment");
687
+ if (r && "getAttribute" in r) {
688
+ const n = (r.getAttribute("style") || "").match(/background-color:\s*([^;]+)/);
689
+ if (n) {
690
+ const i = r.getAttribute("bgcolor") || "";
691
+ return { bgStyle: `background-color: ${n[1].trim()};`, bgAttr: i };
692
+ }
693
+ }
694
+ const e = (o = this.currentNode) == null ? void 0 : o.querySelector(".product-card-wrapper");
695
+ if (e && "getStyle" in e) {
696
+ const s = e.getStyle("background-color");
697
+ if (s && s !== "transparent") {
698
+ const n = "getAttribute" in e && e.getAttribute("bgcolor") || "";
699
+ return { bgStyle: `background-color: ${s};`, bgAttr: n };
700
+ }
701
+ }
702
+ return { bgStyle: "", bgAttr: "" };
703
+ }
704
+ _getControlContainer() {
705
+ const t = this.getContainer();
706
+ return t ? t.querySelector("[data-card-composition-control]") : null;
201
707
  }
202
708
  /**
203
- * Updates orderable state based on layout orientation
204
- * Adds/removes disabled class to hide drag icons for list layout
709
+ * Adds/removes orderable-disabled class based on layout orientation.
710
+ * List layout hides drag handles via CSS and disables draggable attribute
711
+ * to prevent native browser drag-and-drop from working without the handle.
205
712
  */
206
713
  _updateOrderableState() {
207
- const e = this._getCurrentLayout() === "list", i = this.getContainer();
208
- if (!i)
714
+ const r = this._getCurrentLayout() === "list", e = this._getControlContainer();
715
+ if (!e)
209
716
  return;
210
- const o = i.querySelector("[data-card-composition-control]");
211
- if (!o)
212
- return;
213
- const r = o.querySelector("ue-orderable");
214
- r && r.classList.toggle("orderable-disabled", e);
717
+ const o = e.querySelector("[data-composition-list]");
718
+ o && (o.classList.toggle("orderable-disabled", r), o.querySelectorAll(".orderable-item").forEach((s) => {
719
+ s.setAttribute("draggable", r ? "false" : "true");
720
+ }));
215
721
  }
216
722
  /**
217
- * Subscribe to store orientation changes
218
- * Updates orderable state when layout changes via the layout control
723
+ * Disables the "Add Attribute" button when the custom attribute limit is reached
724
+ * or when all available filters have already been added (no unused attributes left).
219
725
  */
220
- _subscribeToOrientationChanges() {
221
- this.unsubscribeOrientation && this.unsubscribeOrientation();
222
- let t = this.store.recommendationConfigs.orientation;
223
- this.unsubscribeOrientation = this.store.$subscribe(() => {
726
+ _updateAddButtonState() {
727
+ const t = this._readCustomAttributesFromNode(), r = t.length >= g, e = new Set(t), o = Object.values(this.store.filterList), s = o.length > 0 && o.every((n) => e.has(n.attributeName));
728
+ this.api.setUIEAttribute(
729
+ j.ADD_ATTRIBUTE,
730
+ $.BUTTON.disabled,
731
+ r || s ? "true" : "false"
732
+ );
733
+ }
734
+ /**
735
+ * Subscribes to store changes for orientation and filterList updates.
736
+ * When filterList changes (e.g. after async fetch), re-renders dropdowns with new options.
737
+ */
738
+ _subscribeToStoreChanges() {
739
+ this.unsubscribeStore && this.unsubscribeStore();
740
+ let t = this.store.recommendationConfigs.orientation, r = Object.keys(this.store.filterList).sort().join(",");
741
+ this.unsubscribeStore = this.store.$subscribe(() => {
224
742
  const e = this.store.recommendationConfigs.orientation;
225
743
  e !== t && (t = e, this._updateOrderableState());
744
+ const o = Object.keys(this.store.filterList).sort().join(",");
745
+ o !== r && (r = o, this._initializeComposition());
226
746
  });
227
747
  }
748
+ /**
749
+ * Looks up the display name for an attribute from the store's filterList.
750
+ * Falls back to Title Case conversion of the snake_case attribute name.
751
+ */
752
+ _getDisplayNameForAttribute(t) {
753
+ const r = Object.values(this.store.filterList).find((e) => e.attributeName === t);
754
+ return r ? r.displayName : ut(t);
755
+ }
756
+ /**
757
+ * Resolves the display content for a custom attribute cell.
758
+ * Uses the real product value from the store when available, falls back to displayName.
759
+ */
760
+ _resolveAttributeContent(t, r, e) {
761
+ var i;
762
+ const s = this.store.recommendationProducts[e], n = (i = s == null ? void 0 : s.product_attributes) == null ? void 0 : i[t];
763
+ return n != null ? String(n) : r;
764
+ }
765
+ _getGridCellHtml(t, r, e, o = "", s = "", n = "") {
766
+ const i = `${m}${t}`, a = w(at, [i]), c = { product_attributes: { [t]: r } };
767
+ let l = a[i](c);
768
+ return l = l.replace("<td", `<td width="${e}%"`), n && (l = l.replace(
769
+ `padding: ${lt}`,
770
+ `padding: ${n}`
771
+ )), o && (l = l.replace(/style="table-layout: fixed;"/, `style="table-layout: fixed; ${o}"`)), s && (l = l.replace(/border="0"/, `border="0" bgcolor="${s}"`)), l;
772
+ }
773
+ _getListRowHtml(t, r, e) {
774
+ const o = w(ct, [e]), s = { product_attributes: { [t]: r } }, i = o[e](s).replace(/<tr>/, "").replace(/<\/tr>/, "");
775
+ return `<tr class="recommendation-attribute-row" ${y}="${e}" ${I}="1">${i}</tr>`;
776
+ }
228
777
  }
229
778
  export {
230
- S as COMPOSITION_CONTROL_BLOCK_ID,
231
- H as RecommendationCardCompositionControl
779
+ ht as COMPOSITION_CONTROL_BLOCK_ID,
780
+ Rt as RecommendationCardCompositionControl
232
781
  };