@schukai/monster 4.63.0 → 4.65.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/CHANGELOG.md +25 -0
- package/package.json +1 -1
- package/source/components/form/buy-box.mjs +2516 -0
- package/source/components/form/cart-control.mjs +710 -0
- package/source/components/form/style/buy-box.pcss +124 -0
- package/source/components/form/style/cart-control.pcss +35 -0
- package/source/components/form/style/variant-select.pcss +79 -0
- package/source/components/form/stylesheet/buy-box.mjs +38 -0
- package/source/components/form/stylesheet/cart-control.mjs +38 -0
- package/source/components/form/stylesheet/variant-select.mjs +38 -0
- package/source/components/form/variant-select.mjs +1483 -0
- package/source/components/host/call-button.mjs +4 -0
- package/source/components/host/config-manager.mjs +4 -0
- package/source/components/host/host.mjs +4 -0
- package/source/components/host/toggle-button.mjs +4 -0
- package/source/components/navigation/table-of-content.mjs +2 -2
- package/source/components/tree-menu/html-tree-menu.mjs +30 -28
- package/source/components/tree-menu/tree-menu.mjs +4 -0
- package/source/monster.mjs +1 -0
- package/test/cases/components/form/buy-box.mjs +181 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
|
|
3
|
+
* Node module: @schukai/monster
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
|
|
6
|
+
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
7
|
+
*
|
|
8
|
+
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
|
|
9
|
+
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
|
|
10
|
+
* For more information about purchasing a commercial license, please contact Volker Schukai.
|
|
11
|
+
*
|
|
12
|
+
* SPDX-License-Identifier: AGPL-3.0
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { instanceSymbol } from "../../constants.mjs";
|
|
16
|
+
import { CustomControl } from "../../dom/customcontrol.mjs";
|
|
17
|
+
import {
|
|
18
|
+
assembleMethodSymbol,
|
|
19
|
+
registerCustomElement,
|
|
20
|
+
} from "../../dom/customelement.mjs";
|
|
21
|
+
import { fireCustomEvent } from "../../dom/events.mjs";
|
|
22
|
+
import { getLocaleOfDocument } from "../../dom/locale.mjs";
|
|
23
|
+
import { getDocument } from "../../dom/util.mjs";
|
|
24
|
+
import { Pathfinder } from "../../data/pathfinder.mjs";
|
|
25
|
+
import { Formatter } from "../../text/formatter.mjs";
|
|
26
|
+
import { isFunction, isObject, isString } from "../../types/is.mjs";
|
|
27
|
+
import { CommonStyleSheet } from "../stylesheet/common.mjs";
|
|
28
|
+
import { FormStyleSheet } from "../stylesheet/form.mjs";
|
|
29
|
+
import { CartControlStyleSheet } from "./stylesheet/cart-control.mjs";
|
|
30
|
+
import "../datatable/datasource/rest.mjs";
|
|
31
|
+
|
|
32
|
+
export { CartControl };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @private
|
|
36
|
+
* @type {symbol}
|
|
37
|
+
*/
|
|
38
|
+
const controlElementSymbol = Symbol("controlElement");
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @private
|
|
42
|
+
* @type {symbol}
|
|
43
|
+
*/
|
|
44
|
+
const countElementSymbol = Symbol("countElement");
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @private
|
|
48
|
+
* @type {symbol}
|
|
49
|
+
*/
|
|
50
|
+
const totalElementSymbol = Symbol("totalElement");
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @private
|
|
54
|
+
* @type {symbol}
|
|
55
|
+
*/
|
|
56
|
+
const statusElementSymbol = Symbol("statusElement");
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @private
|
|
60
|
+
* @type {symbol}
|
|
61
|
+
*/
|
|
62
|
+
const datasourceElementSymbol = Symbol("datasourceElement");
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @private
|
|
66
|
+
* @type {symbol}
|
|
67
|
+
*/
|
|
68
|
+
const cartDataSymbol = Symbol("cartData");
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @private
|
|
72
|
+
* @type {symbol}
|
|
73
|
+
*/
|
|
74
|
+
const pendingPayloadSymbol = Symbol("pendingPayload");
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @private
|
|
78
|
+
* @type {symbol}
|
|
79
|
+
*/
|
|
80
|
+
const pendingRequestSymbol = Symbol("pendingRequest");
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* CartControl
|
|
84
|
+
*
|
|
85
|
+
* @summary Cart control that syncs with a datasource and exposes pending vs verified state.
|
|
86
|
+
* @fires monster-cart-control-pending
|
|
87
|
+
* @fires monster-cart-control-verified
|
|
88
|
+
* @fires monster-cart-control-error
|
|
89
|
+
* @fires monster-cart-control-update
|
|
90
|
+
*/
|
|
91
|
+
class CartControl extends CustomControl {
|
|
92
|
+
static get [instanceSymbol]() {
|
|
93
|
+
return Symbol.for(
|
|
94
|
+
"@schukai/monster/components/form/cart-control@@instance",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @property {Object} templates Template definitions
|
|
100
|
+
* @property {string} templates.main Main template
|
|
101
|
+
* @property {Object} labels Labels
|
|
102
|
+
* @property {Object} datasource Datasource configuration
|
|
103
|
+
* @property {string} datasource.selector Selector for datasource
|
|
104
|
+
* @property {Object} datasource.rest Rest datasource config
|
|
105
|
+
* @property {Object} cart Cart write configuration
|
|
106
|
+
* @property {Object} mapping Response mapping
|
|
107
|
+
* @property {Object} state Control state
|
|
108
|
+
* @property {Object} actions Callback actions
|
|
109
|
+
*/
|
|
110
|
+
get defaults() {
|
|
111
|
+
return Object.assign({}, super.defaults, {
|
|
112
|
+
templates: {
|
|
113
|
+
main: getTemplate(),
|
|
114
|
+
},
|
|
115
|
+
labels: getTranslations(),
|
|
116
|
+
datasource: {
|
|
117
|
+
selector: null,
|
|
118
|
+
rest: null,
|
|
119
|
+
},
|
|
120
|
+
cart: {
|
|
121
|
+
url: null,
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: {
|
|
124
|
+
"Content-Type": "application/json",
|
|
125
|
+
},
|
|
126
|
+
bodyTemplate: null,
|
|
127
|
+
},
|
|
128
|
+
mapping: {
|
|
129
|
+
selector: "*",
|
|
130
|
+
itemsPath: "items",
|
|
131
|
+
totalTemplate: null,
|
|
132
|
+
currencyTemplate: null,
|
|
133
|
+
qtyTemplate: "qty",
|
|
134
|
+
},
|
|
135
|
+
state: {
|
|
136
|
+
status: "idle",
|
|
137
|
+
},
|
|
138
|
+
actions: {
|
|
139
|
+
onpending: null,
|
|
140
|
+
onverified: null,
|
|
141
|
+
onerror: null,
|
|
142
|
+
onupdate: null,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
[assembleMethodSymbol]() {
|
|
148
|
+
super[assembleMethodSymbol]();
|
|
149
|
+
initControlReferences.call(this);
|
|
150
|
+
initDatasource.call(this);
|
|
151
|
+
applyData.call(this);
|
|
152
|
+
setStatus.call(this, this.getOption("state.status") || "idle");
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Re-apply mapped data from options or datasource.
|
|
158
|
+
* @return {CartControl}
|
|
159
|
+
*/
|
|
160
|
+
refresh() {
|
|
161
|
+
applyData.call(this);
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static getTag() {
|
|
166
|
+
return "monster-cart-control";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static getCSSStyleSheet() {
|
|
170
|
+
return [CommonStyleSheet, FormStyleSheet, CartControlStyleSheet];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Add or change an item in the cart.
|
|
175
|
+
* @param {Object} payload
|
|
176
|
+
*/
|
|
177
|
+
addOrChange(payload) {
|
|
178
|
+
const request = buildPayload.call(this, payload);
|
|
179
|
+
this[pendingPayloadSymbol] = payload;
|
|
180
|
+
this[pendingRequestSymbol] = request;
|
|
181
|
+
setStatus.call(this, "pending");
|
|
182
|
+
fireCustomEvent(this, "monster-cart-control-pending", {
|
|
183
|
+
payload: request,
|
|
184
|
+
source: payload,
|
|
185
|
+
});
|
|
186
|
+
const action = this.getOption("actions.onpending");
|
|
187
|
+
if (isFunction(action)) {
|
|
188
|
+
action.call(this, { payload: request, source: payload });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!this[datasourceElementSymbol]) {
|
|
192
|
+
initDatasource.call(this);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!this[datasourceElementSymbol]) {
|
|
196
|
+
handleError.call(this, new Error("cart datasource not configured"));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this[datasourceElementSymbol].data = request;
|
|
201
|
+
this[datasourceElementSymbol].write().catch((e) => {
|
|
202
|
+
handleError.call(this, e);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @private
|
|
209
|
+
*/
|
|
210
|
+
function initControlReferences() {
|
|
211
|
+
this[controlElementSymbol] = this.shadowRoot.querySelector(
|
|
212
|
+
"[data-monster-role=control]",
|
|
213
|
+
);
|
|
214
|
+
this[countElementSymbol] = this.shadowRoot.querySelector(
|
|
215
|
+
"[data-monster-role=count]",
|
|
216
|
+
);
|
|
217
|
+
this[totalElementSymbol] = this.shadowRoot.querySelector(
|
|
218
|
+
"[data-monster-role=total]",
|
|
219
|
+
);
|
|
220
|
+
this[statusElementSymbol] = this.shadowRoot.querySelector(
|
|
221
|
+
"[data-monster-role=status]",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
function initDatasource() {
|
|
229
|
+
if (this[datasourceElementSymbol]) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const selector = this.getOption("datasource.selector");
|
|
233
|
+
if (isString(selector) && selector !== "") {
|
|
234
|
+
this[datasourceElementSymbol] = getDocument().querySelector(selector);
|
|
235
|
+
} else if (isObject(this.getOption("datasource.rest"))) {
|
|
236
|
+
const rest = getDocument().createElement("monster-datasource-rest");
|
|
237
|
+
rest.setOption("read", this.getOption("datasource.rest.read", {}));
|
|
238
|
+
rest.setOption("write", this.getOption("datasource.rest.write", {}));
|
|
239
|
+
rest.setOption("features.autoInit", false);
|
|
240
|
+
this.appendChild(rest);
|
|
241
|
+
this[datasourceElementSymbol] = rest;
|
|
242
|
+
} else if (isString(this.getOption("cart.url"))) {
|
|
243
|
+
const rest = getDocument().createElement("monster-datasource-rest");
|
|
244
|
+
rest.setOption("write.url", this.getOption("cart.url"));
|
|
245
|
+
rest.setOption("write.method", this.getOption("cart.method"));
|
|
246
|
+
rest.setOption("write.init", {
|
|
247
|
+
method: this.getOption("cart.method"),
|
|
248
|
+
headers: this.getOption("cart.headers"),
|
|
249
|
+
});
|
|
250
|
+
rest.setOption("features.autoInit", false);
|
|
251
|
+
this.appendChild(rest);
|
|
252
|
+
this[datasourceElementSymbol] = rest;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this[datasourceElementSymbol]) {
|
|
256
|
+
this[datasourceElementSymbol].setOption(
|
|
257
|
+
"write.responseCallback",
|
|
258
|
+
(payload) => {
|
|
259
|
+
this[datasourceElementSymbol].data = payload;
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
this[datasourceElementSymbol].addEventListener(
|
|
263
|
+
"monster-datasource-fetched",
|
|
264
|
+
(event) => {
|
|
265
|
+
const data = event?.detail?.data || this[datasourceElementSymbol]?.data;
|
|
266
|
+
if (data) {
|
|
267
|
+
this[cartDataSymbol] = data;
|
|
268
|
+
applyData.call(this);
|
|
269
|
+
}
|
|
270
|
+
setStatus.call(this, "verified");
|
|
271
|
+
fireCustomEvent(this, "monster-cart-control-verified", {
|
|
272
|
+
data,
|
|
273
|
+
payload: this[pendingRequestSymbol],
|
|
274
|
+
source: this[pendingPayloadSymbol],
|
|
275
|
+
});
|
|
276
|
+
const action = this.getOption("actions.onverified");
|
|
277
|
+
if (isFunction(action)) {
|
|
278
|
+
action.call(this, {
|
|
279
|
+
data,
|
|
280
|
+
payload: this[pendingRequestSymbol],
|
|
281
|
+
source: this[pendingPayloadSymbol],
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
this[pendingPayloadSymbol] = null;
|
|
285
|
+
this[pendingRequestSymbol] = null;
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
this[datasourceElementSymbol].addEventListener(
|
|
289
|
+
"monster-datasource-error",
|
|
290
|
+
(event) => {
|
|
291
|
+
handleError.call(this, event?.detail?.error);
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
function applyData() {
|
|
301
|
+
const data = this[cartDataSymbol] || this.getOption("data");
|
|
302
|
+
if (!data) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const mapping = this.getOption("mapping", {});
|
|
307
|
+
let source = data;
|
|
308
|
+
if (
|
|
309
|
+
isString(mapping?.selector) &&
|
|
310
|
+
mapping.selector !== "*" &&
|
|
311
|
+
mapping.selector
|
|
312
|
+
) {
|
|
313
|
+
try {
|
|
314
|
+
source = new Pathfinder(data).getVia(mapping.selector);
|
|
315
|
+
} catch (_e) {
|
|
316
|
+
source = data;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const itemsPath = mapping.itemsPath;
|
|
321
|
+
const items = itemsPath
|
|
322
|
+
? new Pathfinder(source).getVia(itemsPath)
|
|
323
|
+
: source?.items;
|
|
324
|
+
const count = Array.isArray(items)
|
|
325
|
+
? items.reduce((sum, item) => {
|
|
326
|
+
const qty = readNumber(item, mapping.qtyTemplate) ?? 0;
|
|
327
|
+
return sum + qty;
|
|
328
|
+
}, 0)
|
|
329
|
+
: 0;
|
|
330
|
+
const total = readNumber(source, mapping.totalTemplate);
|
|
331
|
+
const currency = readString(source, mapping.currencyTemplate) || "EUR";
|
|
332
|
+
|
|
333
|
+
if (this[countElementSymbol]) {
|
|
334
|
+
setElementText(
|
|
335
|
+
this[countElementSymbol],
|
|
336
|
+
formatCount(count, this.getOption("labels.items")),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (this[totalElementSymbol]) {
|
|
340
|
+
setElementText(
|
|
341
|
+
this[totalElementSymbol],
|
|
342
|
+
total !== null ? formatMoney(total, currency) : "",
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fireCustomEvent(this, "monster-cart-control-update", {
|
|
347
|
+
count,
|
|
348
|
+
total,
|
|
349
|
+
currency,
|
|
350
|
+
items,
|
|
351
|
+
});
|
|
352
|
+
const action = this.getOption("actions.onupdate");
|
|
353
|
+
if (isFunction(action)) {
|
|
354
|
+
action.call(this, { count, total, currency, items });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @private
|
|
360
|
+
* @param {string} status
|
|
361
|
+
*/
|
|
362
|
+
function setStatus(status) {
|
|
363
|
+
this.setOption("state.status", status);
|
|
364
|
+
if (!this[statusElementSymbol]) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
let label = "";
|
|
368
|
+
if (status === "pending") {
|
|
369
|
+
label = this.getOption("labels.statusPending");
|
|
370
|
+
} else if (status === "verified") {
|
|
371
|
+
label = this.getOption("labels.statusVerified");
|
|
372
|
+
} else if (status === "error") {
|
|
373
|
+
label = this.getOption("labels.statusError");
|
|
374
|
+
} else {
|
|
375
|
+
label = this.getOption("labels.statusIdle");
|
|
376
|
+
}
|
|
377
|
+
setElementText(this[statusElementSymbol], label);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @private
|
|
382
|
+
* @param {Error} error
|
|
383
|
+
*/
|
|
384
|
+
function handleError(error) {
|
|
385
|
+
setStatus.call(this, "error");
|
|
386
|
+
fireCustomEvent(this, "monster-cart-control-error", {
|
|
387
|
+
error,
|
|
388
|
+
payload: this[pendingRequestSymbol],
|
|
389
|
+
source: this[pendingPayloadSymbol],
|
|
390
|
+
});
|
|
391
|
+
const action = this.getOption("actions.onerror");
|
|
392
|
+
if (isFunction(action)) {
|
|
393
|
+
action.call(this, {
|
|
394
|
+
error,
|
|
395
|
+
payload: this[pendingRequestSymbol],
|
|
396
|
+
source: this[pendingPayloadSymbol],
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
this[pendingPayloadSymbol] = null;
|
|
400
|
+
this[pendingRequestSymbol] = null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @private
|
|
405
|
+
* @param {Object} payload
|
|
406
|
+
* @return {Object}
|
|
407
|
+
*/
|
|
408
|
+
function buildPayload(payload) {
|
|
409
|
+
if (!payload) {
|
|
410
|
+
return {};
|
|
411
|
+
}
|
|
412
|
+
const data = Object.assign({}, payload);
|
|
413
|
+
const template = this.getOption("cart.bodyTemplate");
|
|
414
|
+
if (isString(template) && template.includes("${")) {
|
|
415
|
+
const formatted = new Formatter(stringifyForTemplate(data)).format(
|
|
416
|
+
template,
|
|
417
|
+
);
|
|
418
|
+
try {
|
|
419
|
+
return JSON.parse(formatted);
|
|
420
|
+
} catch (_e) {
|
|
421
|
+
return { value: formatted };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return data;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @private
|
|
429
|
+
* @param {Object|Array|string|number|boolean|null} value
|
|
430
|
+
* @return {Object|Array|string}
|
|
431
|
+
*/
|
|
432
|
+
function stringifyForTemplate(value) {
|
|
433
|
+
if (value === null || value === undefined) {
|
|
434
|
+
return "";
|
|
435
|
+
}
|
|
436
|
+
if (Array.isArray(value)) {
|
|
437
|
+
return value.map((entry) => stringifyForTemplate(entry));
|
|
438
|
+
}
|
|
439
|
+
if (typeof value === "object") {
|
|
440
|
+
const result = {};
|
|
441
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
442
|
+
result[key] = stringifyForTemplate(entry);
|
|
443
|
+
}
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
return `${value}`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @private
|
|
451
|
+
* @param {Object} source
|
|
452
|
+
* @param {string|null} template
|
|
453
|
+
* @return {string|null}
|
|
454
|
+
*/
|
|
455
|
+
function readString(source, template) {
|
|
456
|
+
if (!isString(template) || template === "") {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
if (template.includes("${")) {
|
|
461
|
+
return new Formatter(source).format(template);
|
|
462
|
+
}
|
|
463
|
+
return new Pathfinder(source).getVia(template);
|
|
464
|
+
} catch (_e) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* @private
|
|
471
|
+
* @param {Object} source
|
|
472
|
+
* @param {string|null} template
|
|
473
|
+
* @return {number|null}
|
|
474
|
+
*/
|
|
475
|
+
function readNumber(source, template) {
|
|
476
|
+
const value = readString(source, template);
|
|
477
|
+
if (value === null || value === undefined) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const num = Number(value);
|
|
481
|
+
return Number.isFinite(num) ? num : null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @private
|
|
486
|
+
* @param {number} value
|
|
487
|
+
* @param {string} label
|
|
488
|
+
* @return {string}
|
|
489
|
+
*/
|
|
490
|
+
function formatCount(value, label) {
|
|
491
|
+
const safeValue = Number.isFinite(value) ? value : 0;
|
|
492
|
+
return `${safeValue} ${label}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @private
|
|
497
|
+
* @param {number} value
|
|
498
|
+
* @param {string} currency
|
|
499
|
+
* @return {string}
|
|
500
|
+
*/
|
|
501
|
+
function formatMoney(value, currency) {
|
|
502
|
+
try {
|
|
503
|
+
const locale = getLocaleOfDocument();
|
|
504
|
+
return new Intl.NumberFormat(locale.locale, {
|
|
505
|
+
style: "currency",
|
|
506
|
+
currency,
|
|
507
|
+
}).format(value);
|
|
508
|
+
} catch (_e) {
|
|
509
|
+
return `${value} ${currency}`;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* @private
|
|
515
|
+
* @param {HTMLElement} element
|
|
516
|
+
* @param {string} text
|
|
517
|
+
*/
|
|
518
|
+
function setElementText(element, text) {
|
|
519
|
+
element.textContent = text;
|
|
520
|
+
element.hidden = text === "";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @private
|
|
525
|
+
* @return {string}
|
|
526
|
+
*/
|
|
527
|
+
function getTemplate() {
|
|
528
|
+
// language=HTML
|
|
529
|
+
return `
|
|
530
|
+
<div data-monster-role="control" part="control">
|
|
531
|
+
<div data-monster-role="summary" part="summary">
|
|
532
|
+
<div data-monster-role="count" part="count"></div>
|
|
533
|
+
<div data-monster-role="total" part="total"></div>
|
|
534
|
+
</div>
|
|
535
|
+
<div data-monster-role="status" part="status"></div>
|
|
536
|
+
</div>
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* @private
|
|
542
|
+
* @returns {object}
|
|
543
|
+
*/
|
|
544
|
+
function getTranslations() {
|
|
545
|
+
const locale = getLocaleOfDocument();
|
|
546
|
+
switch (locale.language) {
|
|
547
|
+
case "de":
|
|
548
|
+
return {
|
|
549
|
+
items: "Artikel",
|
|
550
|
+
statusIdle: "Warenkorb bereit",
|
|
551
|
+
statusPending: "Warenkorb wird aktualisiert",
|
|
552
|
+
statusVerified: "Warenkorb bestaetigt",
|
|
553
|
+
statusError: "Warenkorb Fehler",
|
|
554
|
+
};
|
|
555
|
+
case "es":
|
|
556
|
+
return {
|
|
557
|
+
items: "Articulos",
|
|
558
|
+
statusIdle: "Carrito listo",
|
|
559
|
+
statusPending: "Carrito actualizando",
|
|
560
|
+
statusVerified: "Carrito verificado",
|
|
561
|
+
statusError: "Error del carrito",
|
|
562
|
+
};
|
|
563
|
+
case "zh":
|
|
564
|
+
return {
|
|
565
|
+
items: "商品",
|
|
566
|
+
statusIdle: "购物车已就绪",
|
|
567
|
+
statusPending: "购物车更新中",
|
|
568
|
+
statusVerified: "购物车已确认",
|
|
569
|
+
statusError: "购物车错误",
|
|
570
|
+
};
|
|
571
|
+
case "hi":
|
|
572
|
+
return {
|
|
573
|
+
items: "आइटम",
|
|
574
|
+
statusIdle: "कार्ट तैयार",
|
|
575
|
+
statusPending: "कार्ट अपडेट हो रहा है",
|
|
576
|
+
statusVerified: "कार्ट सत्यापित",
|
|
577
|
+
statusError: "कार्ट त्रुटि",
|
|
578
|
+
};
|
|
579
|
+
case "bn":
|
|
580
|
+
return {
|
|
581
|
+
items: "আইটেম",
|
|
582
|
+
statusIdle: "কার্ট প্রস্তুত",
|
|
583
|
+
statusPending: "কার্ট আপডেট হচ্ছে",
|
|
584
|
+
statusVerified: "কার্ট নিশ্চিত",
|
|
585
|
+
statusError: "কার্ট ত্রুটি",
|
|
586
|
+
};
|
|
587
|
+
case "pt":
|
|
588
|
+
return {
|
|
589
|
+
items: "Itens",
|
|
590
|
+
statusIdle: "Carrinho pronto",
|
|
591
|
+
statusPending: "Carrinho atualizando",
|
|
592
|
+
statusVerified: "Carrinho verificado",
|
|
593
|
+
statusError: "Erro no carrinho",
|
|
594
|
+
};
|
|
595
|
+
case "ru":
|
|
596
|
+
return {
|
|
597
|
+
items: "Tovary",
|
|
598
|
+
statusIdle: "Korzina gotova",
|
|
599
|
+
statusPending: "Korzina obnovlyaetsya",
|
|
600
|
+
statusVerified: "Korzina podtverzhdena",
|
|
601
|
+
statusError: "Oshibka korziny",
|
|
602
|
+
};
|
|
603
|
+
case "ja":
|
|
604
|
+
return {
|
|
605
|
+
items: "商品",
|
|
606
|
+
statusIdle: "カート準備完了",
|
|
607
|
+
statusPending: "カート更新中",
|
|
608
|
+
statusVerified: "カート確認済み",
|
|
609
|
+
statusError: "カートエラー",
|
|
610
|
+
};
|
|
611
|
+
case "pa":
|
|
612
|
+
return {
|
|
613
|
+
items: "ਆਈਟਮ",
|
|
614
|
+
statusIdle: "ਕਾਰਟ ਤਿਆਰ",
|
|
615
|
+
statusPending: "ਕਾਰਟ ਅੱਪਡੇਟ ਹੋ ਰਿਹਾ ਹੈ",
|
|
616
|
+
statusVerified: "ਕਾਰਟ ਪੁਸ਼ਟੀਤ",
|
|
617
|
+
statusError: "ਕਾਰਟ ਗਲਤੀ",
|
|
618
|
+
};
|
|
619
|
+
case "mr":
|
|
620
|
+
return {
|
|
621
|
+
items: "आयटम",
|
|
622
|
+
statusIdle: "कार्ट तयार",
|
|
623
|
+
statusPending: "कार्ट अद्ययावत होत आहे",
|
|
624
|
+
statusVerified: "कार्ट सत्यापित",
|
|
625
|
+
statusError: "कार्ट त्रुटी",
|
|
626
|
+
};
|
|
627
|
+
case "fr":
|
|
628
|
+
return {
|
|
629
|
+
items: "Articles",
|
|
630
|
+
statusIdle: "Panier pret",
|
|
631
|
+
statusPending: "Panier en mise a jour",
|
|
632
|
+
statusVerified: "Panier verifie",
|
|
633
|
+
statusError: "Erreur du panier",
|
|
634
|
+
};
|
|
635
|
+
case "it":
|
|
636
|
+
return {
|
|
637
|
+
items: "Articoli",
|
|
638
|
+
statusIdle: "Carrello pronto",
|
|
639
|
+
statusPending: "Carrello in aggiornamento",
|
|
640
|
+
statusVerified: "Carrello verificato",
|
|
641
|
+
statusError: "Errore carrello",
|
|
642
|
+
};
|
|
643
|
+
case "nl":
|
|
644
|
+
return {
|
|
645
|
+
items: "Artikelen",
|
|
646
|
+
statusIdle: "Winkelwagen klaar",
|
|
647
|
+
statusPending: "Winkelwagen bijwerken",
|
|
648
|
+
statusVerified: "Winkelwagen bevestigd",
|
|
649
|
+
statusError: "Winkelwagen fout",
|
|
650
|
+
};
|
|
651
|
+
case "sv":
|
|
652
|
+
return {
|
|
653
|
+
items: "Artiklar",
|
|
654
|
+
statusIdle: "Kundvagn redo",
|
|
655
|
+
statusPending: "Kundvagn uppdateras",
|
|
656
|
+
statusVerified: "Kundvagn verifierad",
|
|
657
|
+
statusError: "Kundvagn fel",
|
|
658
|
+
};
|
|
659
|
+
case "pl":
|
|
660
|
+
return {
|
|
661
|
+
items: "Produkty",
|
|
662
|
+
statusIdle: "Koszyk gotowy",
|
|
663
|
+
statusPending: "Koszyk aktualizowany",
|
|
664
|
+
statusVerified: "Koszyk potwierdzony",
|
|
665
|
+
statusError: "Blad koszyka",
|
|
666
|
+
};
|
|
667
|
+
case "da":
|
|
668
|
+
return {
|
|
669
|
+
items: "Varer",
|
|
670
|
+
statusIdle: "Kurv klar",
|
|
671
|
+
statusPending: "Kurv opdateres",
|
|
672
|
+
statusVerified: "Kurv bekraeftet",
|
|
673
|
+
statusError: "Kurv fejl",
|
|
674
|
+
};
|
|
675
|
+
case "fi":
|
|
676
|
+
return {
|
|
677
|
+
items: "Tuotteet",
|
|
678
|
+
statusIdle: "Ostoskori valmis",
|
|
679
|
+
statusPending: "Ostoskoria paivitetaan",
|
|
680
|
+
statusVerified: "Ostoskori vahvistettu",
|
|
681
|
+
statusError: "Ostoskorin virhe",
|
|
682
|
+
};
|
|
683
|
+
case "no":
|
|
684
|
+
return {
|
|
685
|
+
items: "Varer",
|
|
686
|
+
statusIdle: "Handlekurv klar",
|
|
687
|
+
statusPending: "Handlekurv oppdateres",
|
|
688
|
+
statusVerified: "Handlekurv bekreftet",
|
|
689
|
+
statusError: "Handlekurv feil",
|
|
690
|
+
};
|
|
691
|
+
case "cs":
|
|
692
|
+
return {
|
|
693
|
+
items: "Polozky",
|
|
694
|
+
statusIdle: "Kosik pripraven",
|
|
695
|
+
statusPending: "Kosik se aktualizuje",
|
|
696
|
+
statusVerified: "Kosik potvrzen",
|
|
697
|
+
statusError: "Chyba kosiku",
|
|
698
|
+
};
|
|
699
|
+
default:
|
|
700
|
+
return {
|
|
701
|
+
items: "Items",
|
|
702
|
+
statusIdle: "Cart ready",
|
|
703
|
+
statusPending: "Cart updating",
|
|
704
|
+
statusVerified: "Cart verified",
|
|
705
|
+
statusError: "Cart error",
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
registerCustomElement(CartControl);
|