@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.
- package/dist/components/organisms/header/EditorActions.vue.js +10 -8
- package/dist/components/organisms/header/EditorActions.vue2.js +41 -31
- package/dist/components/organisms/header/MigrationConfirmModal.vue.js +17 -0
- package/dist/components/organisms/header/MigrationConfirmModal.vue2.js +39 -0
- package/dist/components/organisms/onboarding/GenericOnboarding.vue.js +1 -1
- package/dist/components/organisms/onboarding/GenericOnboarding.vue2.js +1 -1
- package/dist/components/organisms/onboarding/TextBlockOnboarding.vue.js +1 -1
- package/dist/components/organisms/onboarding/TextBlockOnboarding.vue2.js +2 -2
- package/dist/config/compiler/unsubscribeCompilerRules.js +14 -14
- package/dist/config/compiler/utils/recommendationCompilerUtils.js +29 -18
- package/dist/config/i18n/en/labels.json.js +8 -3
- package/dist/config/migrator/itemsBlockMigrator.js +135 -131
- package/dist/config/migrator/recommendationMigrator.js +58 -54
- package/dist/enums/block.js +4 -0
- package/dist/extensions/Blocks/Items/block.js +30 -21
- package/dist/extensions/Blocks/Items/iconsRegistry.js +7 -6
- package/dist/extensions/Blocks/Items/items.css.js +48 -0
- package/dist/extensions/Blocks/Recommendation/block.js +64 -34
- package/dist/extensions/Blocks/Recommendation/constants/blockIds.js +1 -1
- package/dist/extensions/Blocks/Recommendation/constants/controlIds.js +1 -1
- package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +36 -34
- package/dist/extensions/Blocks/Recommendation/constants/selectors.js +15 -12
- package/dist/extensions/Blocks/Recommendation/controls/cardBackground/index.js +4 -4
- package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +693 -144
- package/dist/extensions/Blocks/Recommendation/controls/customAttribute/index.js +78 -0
- package/dist/extensions/Blocks/Recommendation/controls/main/algorithm.js +15 -15
- package/dist/extensions/Blocks/Recommendation/controls/main/currency.js +24 -24
- package/dist/extensions/Blocks/Recommendation/controls/main/filters.js +2 -2
- package/dist/extensions/Blocks/Recommendation/controls/main/index.js +107 -78
- package/dist/extensions/Blocks/Recommendation/controls/{layout/index.js → main/layoutOrientation.js} +34 -23
- package/dist/extensions/Blocks/Recommendation/controls/main/locale.js +2 -2
- package/dist/extensions/Blocks/Recommendation/controls/main/productCount.js +58 -0
- package/dist/extensions/Blocks/Recommendation/controls/main/productLayout.js +150 -64
- package/dist/extensions/Blocks/Recommendation/controls/main/shuffle.js +2 -2
- package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +202 -200
- package/dist/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.js +25 -8
- package/dist/extensions/Blocks/Recommendation/controls/name/textTrim.js +6 -5
- package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textAfter.js +8 -8
- package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textBefore.js +21 -21
- package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textAfter.js +13 -13
- package/dist/extensions/Blocks/Recommendation/controls/omnibusPrice/textBefore.js +17 -17
- package/dist/extensions/Blocks/Recommendation/controls/spacing/index.js +94 -100
- package/dist/extensions/Blocks/Recommendation/controls/syncInfoMessage.js +65 -0
- package/dist/extensions/Blocks/Recommendation/extension.js +20 -18
- package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +5 -4
- package/dist/extensions/Blocks/Recommendation/recommendation.css.js +209 -2
- package/dist/extensions/Blocks/Recommendation/settingsPanel.js +135 -111
- package/dist/extensions/Blocks/Recommendation/store/recommendation.js +9 -7
- package/dist/extensions/Blocks/Recommendation/templates/grid/elementRenderer.js +63 -34
- package/dist/extensions/Blocks/Recommendation/templates/grid/template.js +25 -28
- package/dist/extensions/Blocks/Recommendation/templates/index.js +8 -8
- package/dist/extensions/Blocks/Recommendation/templates/list/elementRenderer.js +28 -13
- package/dist/extensions/Blocks/Recommendation/templates/list/template.js +25 -44
- package/dist/extensions/Blocks/Recommendation/templates/utils.js +62 -38
- package/dist/extensions/Blocks/common-control.js +96 -39
- package/dist/guido.css +1 -1
- package/dist/src/@types/extensions/block.d.ts +2 -0
- package/dist/src/App.vue.d.ts +3 -1
- package/dist/src/components/organisms/header/EditorActions.vue.d.ts +1 -1
- package/dist/src/components/organisms/header/MigrationConfirmModal.vue.d.ts +6 -0
- package/dist/src/components/wrappers/WpModal.vue.d.ts +2 -2
- package/dist/src/enums/block.d.ts +4 -0
- package/dist/src/extensions/Blocks/Items/block.d.ts +3 -1
- package/dist/src/extensions/Blocks/Recommendation/block.d.ts +4 -1
- package/dist/src/extensions/Blocks/Recommendation/constants/blockIds.d.ts +2 -1
- package/dist/src/extensions/Blocks/Recommendation/constants/controlIds.d.ts +9 -1
- package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
- package/dist/src/extensions/Blocks/Recommendation/constants/selectors.d.ts +10 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/cardComposition/index.d.ts +134 -44
- package/dist/src/extensions/Blocks/Recommendation/controls/customAttribute/index.d.ts +105 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/index.d.ts +3 -2
- package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +5 -1
- package/dist/src/extensions/Blocks/Recommendation/controls/{layout/index.d.ts → main/layoutOrientation.d.ts} +3 -3
- package/dist/src/extensions/Blocks/Recommendation/controls/main/productCount.d.ts +28 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/main/productLayout.d.ts +38 -20
- package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +6 -2
- package/dist/src/extensions/Blocks/Recommendation/controls/mobileLayout/cssRules.d.ts +23 -1
- package/dist/src/extensions/Blocks/Recommendation/controls/spacing/index.d.ts +8 -18
- package/dist/src/extensions/Blocks/Recommendation/controls/syncInfoMessage.d.ts +34 -0
- package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +2 -0
- package/dist/src/extensions/Blocks/Recommendation/templates/grid/elementRenderer.d.ts +1 -1
- package/dist/src/extensions/Blocks/Recommendation/templates/list/elementRenderer.d.ts +1 -1
- package/dist/src/extensions/Blocks/Recommendation/templates/list/template.d.ts +10 -4
- package/dist/src/extensions/Blocks/Recommendation/templates/utils.d.ts +37 -2
- package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +13 -0
- package/dist/src/extensions/Blocks/common-control.d.ts +29 -2
- package/dist/src/stores/template.d.ts +29 -0
- package/dist/src/utils/migrationBannerHtml.d.ts +2 -0
- package/dist/static/assets/info.svg.js +5 -0
- package/dist/static/styles/components/wide-panel.css.js +1 -0
- package/dist/static/styles/customEditorStyle.css.js +9 -0
- package/dist/static/styles/variables.css.js +3 -0
- package/dist/stores/template.js +15 -0
- package/dist/utils/migrationBannerHtml.js +21 -0
- package/package.json +3 -2
- package/dist/src/extensions/Blocks/Recommendation/controls/cardBackgroundColorControl.d.ts +0 -25
- package/dist/src/extensions/Blocks/Recommendation/controls/omnibusDiscountTextAfterControl.d.ts +0 -15
- package/dist/src/extensions/Blocks/Recommendation/controls/omnibusDiscountTextBeforeControl.d.ts +0 -15
- package/dist/src/extensions/Blocks/Recommendation/controls/omnibusPriceTextAfterControl.d.ts +0 -15
- package/dist/src/extensions/Blocks/Recommendation/controls/omnibusPriceTextBeforeControl.d.ts +0 -15
- package/dist/src/extensions/Blocks/Recommendation/controls/spacingControl.d.ts +0 -60
|
@@ -1,232 +1,781 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import { ModificationDescription as
|
|
5
|
-
import { CommonControl as
|
|
6
|
-
import { ATTR_PRODUCT_IMAGE as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ key:
|
|
19
|
-
{ key:
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
39
|
+
return ht;
|
|
29
40
|
}
|
|
41
|
+
// ========================================================================
|
|
42
|
+
// Lifecycle
|
|
43
|
+
// ========================================================================
|
|
30
44
|
getTemplate() {
|
|
31
|
-
const t =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
<span
|
|
57
|
-
|
|
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
|
-
*
|
|
296
|
+
* Builds select options from the store's filterList.
|
|
297
|
+
* Falls back to a single option for the currently selected attribute.
|
|
63
298
|
*/
|
|
64
|
-
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
*
|
|
85
|
-
*
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
*
|
|
101
|
-
*
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
*
|
|
129
|
-
*
|
|
486
|
+
* Removes a single custom attribute by its index in the customAttrs array.
|
|
487
|
+
* Index-based to correctly handle duplicate attributes.
|
|
130
488
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
*
|
|
144
|
-
*
|
|
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
|
-
|
|
584
|
+
_removeCustomAttributeHtml(t) {
|
|
148
585
|
if (!this.currentNode)
|
|
149
586
|
return;
|
|
150
|
-
const
|
|
151
|
-
|
|
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
|
-
*
|
|
155
|
-
*
|
|
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
|
|
161
|
-
if (!(
|
|
623
|
+
const r = this.currentNode.querySelectorAll(N);
|
|
624
|
+
if (!(r != null && r.length))
|
|
162
625
|
return;
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
}),
|
|
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,
|
|
173
|
-
return
|
|
174
|
-
const
|
|
175
|
-
return
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
188
|
-
if (!(
|
|
648
|
+
const e = this.currentNode.querySelectorAll(`${A}[${y}="${t}"]`);
|
|
649
|
+
if (!(e != null && e.length))
|
|
189
650
|
return;
|
|
190
|
-
const o =
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
}),
|
|
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
|
-
*
|
|
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 ||
|
|
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
|
-
*
|
|
204
|
-
*
|
|
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
|
|
208
|
-
if (!
|
|
714
|
+
const r = this._getCurrentLayout() === "list", e = this._getControlContainer();
|
|
715
|
+
if (!e)
|
|
209
716
|
return;
|
|
210
|
-
const o =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
*
|
|
218
|
-
*
|
|
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
|
-
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
231
|
-
|
|
779
|
+
ht as COMPOSITION_CONTROL_BLOCK_ID,
|
|
780
|
+
Rt as RecommendationCardCompositionControl
|
|
232
781
|
};
|