@gengage/assistant-fe 0.1.8 → 0.2.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 (138) hide show
  1. package/README.md +79 -286
  2. package/dist/assistant-fe.css +1 -1
  3. package/dist/chat/api.d.ts +1 -12
  4. package/dist/chat/api.d.ts.map +1 -1
  5. package/dist/chat/components/CategoriesContainer.d.ts.map +1 -1
  6. package/dist/chat/components/ChatDrawer.d.ts +15 -1
  7. package/dist/chat/components/ChatDrawer.d.ts.map +1 -1
  8. package/dist/chat/components/ChoicePrompter.d.ts +1 -0
  9. package/dist/chat/components/ChoicePrompter.d.ts.map +1 -1
  10. package/dist/chat/components/ComparisonTable.d.ts +5 -3
  11. package/dist/chat/components/ComparisonTable.d.ts.map +1 -1
  12. package/dist/chat/components/PanelTopBar.d.ts +2 -0
  13. package/dist/chat/components/PanelTopBar.d.ts.map +1 -1
  14. package/dist/chat/components/ProductSummaryCard.d.ts.map +1 -1
  15. package/dist/chat/components/ReviewHighlights.d.ts +3 -0
  16. package/dist/chat/components/ReviewHighlights.d.ts.map +1 -1
  17. package/dist/chat/components/renderUISpec.d.ts +0 -5
  18. package/dist/chat/components/renderUISpec.d.ts.map +1 -1
  19. package/dist/chat/index.d.ts +18 -9
  20. package/dist/chat/index.d.ts.map +1 -1
  21. package/dist/chat/locales/en.d.ts.map +1 -1
  22. package/dist/chat/locales/tr.d.ts.map +1 -1
  23. package/dist/chat/session-persistence.d.ts +3 -0
  24. package/dist/chat/session-persistence.d.ts.map +1 -1
  25. package/dist/chat/types.d.ts +23 -25
  26. package/dist/chat/types.d.ts.map +1 -1
  27. package/dist/chat.cjs +1 -1
  28. package/dist/chat.iife.js +22 -22
  29. package/dist/chat.iife.js.map +1 -1
  30. package/dist/chat.js +8 -9
  31. package/dist/chat.js.map +1 -1
  32. package/dist/common/action-router.d.ts.map +1 -1
  33. package/dist/common/activity-tracker.d.ts +1 -1
  34. package/dist/common/analytics.d.ts +12 -8
  35. package/dist/common/analytics.d.ts.map +1 -1
  36. package/dist/common/api-paths.d.ts +2 -5
  37. package/dist/common/api-paths.d.ts.map +1 -1
  38. package/dist/common/config-schema.d.ts.map +1 -1
  39. package/dist/common/debug.d.ts +14 -0
  40. package/dist/common/debug.d.ts.map +1 -0
  41. package/dist/common/events.d.ts.map +1 -1
  42. package/dist/common/index.d.ts +2 -2
  43. package/dist/common/index.d.ts.map +1 -1
  44. package/dist/common/native-webview.d.ts.map +1 -1
  45. package/dist/common/overlay.d.ts +19 -0
  46. package/dist/common/overlay.d.ts.map +1 -1
  47. package/dist/common/product-utils.d.ts +10 -0
  48. package/dist/common/product-utils.d.ts.map +1 -1
  49. package/dist/common/{v1-protocol-adapter.d.ts → protocol-adapter.d.ts} +2 -2
  50. package/dist/common/protocol-adapter.d.ts.map +1 -0
  51. package/dist/common/quantity-stepper.d.ts +10 -10
  52. package/dist/common/quantity-stepper.d.ts.map +1 -1
  53. package/dist/common/safe-html.d.ts.map +1 -1
  54. package/dist/common/streaming.d.ts.map +1 -1
  55. package/dist/common/tts-player.d.ts +2 -0
  56. package/dist/common/tts-player.d.ts.map +1 -1
  57. package/dist/common/types.d.ts +1 -1
  58. package/dist/common/voice-input.d.ts +1 -0
  59. package/dist/common/voice-input.d.ts.map +1 -1
  60. package/dist/common/widget-base.d.ts.map +1 -1
  61. package/dist/common.cjs +1 -1
  62. package/dist/common.cjs.map +1 -1
  63. package/dist/common.js +173 -172
  64. package/dist/common.js.map +1 -1
  65. package/dist/index-BA7N_XOO.cjs +2 -0
  66. package/dist/index-BA7N_XOO.cjs.map +1 -0
  67. package/dist/{index-CcOJTzYu.js → index-C6KDzSjm.js} +155 -159
  68. package/dist/index-C6KDzSjm.js.map +1 -0
  69. package/dist/index-RmQRBt6w.js +4449 -0
  70. package/dist/index-RmQRBt6w.js.map +1 -0
  71. package/dist/index-VgLdYuZV.cjs +13 -0
  72. package/dist/index-VgLdYuZV.cjs.map +1 -0
  73. package/dist/index.cjs +1 -1
  74. package/dist/index.js +16 -16
  75. package/dist/native.cjs +1 -1
  76. package/dist/native.iife.js +23 -23
  77. package/dist/native.iife.js.map +1 -1
  78. package/dist/native.js +1 -1
  79. package/dist/qna/index.d.ts +19 -0
  80. package/dist/qna/index.d.ts.map +1 -1
  81. package/dist/qna/types.d.ts +2 -2
  82. package/dist/qna/types.d.ts.map +1 -1
  83. package/dist/qna.cjs +1 -1
  84. package/dist/qna.cjs.map +1 -1
  85. package/dist/qna.css +1 -1
  86. package/dist/qna.iife.js +7 -7
  87. package/dist/qna.iife.js.map +1 -1
  88. package/dist/qna.js +14 -14
  89. package/dist/qna.js.map +1 -1
  90. package/dist/quantity-stepper-BKtPQUR1.js +78 -0
  91. package/dist/quantity-stepper-BKtPQUR1.js.map +1 -0
  92. package/dist/quantity-stepper-DU6va4sS.cjs +2 -0
  93. package/dist/quantity-stepper-DU6va4sS.cjs.map +1 -0
  94. package/dist/{schemas-yF4IOEUi.js → schemas-BAEbjFPE.js} +1181 -957
  95. package/dist/schemas-BAEbjFPE.js.map +1 -0
  96. package/dist/schemas-DIyHm5pa.cjs +86 -0
  97. package/dist/schemas-DIyHm5pa.cjs.map +1 -0
  98. package/dist/simrel/api.d.ts +1 -1
  99. package/dist/simrel/api.d.ts.map +1 -1
  100. package/dist/simrel/components/GroupTabs.d.ts +1 -1
  101. package/dist/simrel/components/GroupTabs.d.ts.map +1 -1
  102. package/dist/simrel/components/ProductCard.d.ts +1 -1
  103. package/dist/simrel/components/ProductCard.d.ts.map +1 -1
  104. package/dist/simrel/components/ProductGrid.d.ts +1 -1
  105. package/dist/simrel/components/ProductGrid.d.ts.map +1 -1
  106. package/dist/simrel/index.d.ts +20 -1
  107. package/dist/simrel/index.d.ts.map +1 -1
  108. package/dist/simrel/locales/en.d.ts.map +1 -1
  109. package/dist/simrel/locales/tr.d.ts.map +1 -1
  110. package/dist/simrel/types.d.ts +2 -0
  111. package/dist/simrel/types.d.ts.map +1 -1
  112. package/dist/simrel.cjs +1 -1
  113. package/dist/simrel.cjs.map +1 -1
  114. package/dist/simrel.css +1 -1
  115. package/dist/simrel.iife.js +11 -11
  116. package/dist/simrel.iife.js.map +1 -1
  117. package/dist/simrel.js +281 -267
  118. package/dist/simrel.js.map +1 -1
  119. package/package.json +1 -1
  120. package/dist/chat/components/ProactivePopup.d.ts +0 -20
  121. package/dist/chat/components/ProactivePopup.d.ts.map +0 -1
  122. package/dist/chat/heartbeat.d.ts +0 -71
  123. package/dist/chat/heartbeat.d.ts.map +0 -1
  124. package/dist/common/v1-protocol-adapter.d.ts.map +0 -1
  125. package/dist/index-BqIzV4ni.cjs +0 -2
  126. package/dist/index-BqIzV4ni.cjs.map +0 -1
  127. package/dist/index-CcOJTzYu.js.map +0 -1
  128. package/dist/index-DZ3Mi5xF.cjs +0 -13
  129. package/dist/index-DZ3Mi5xF.cjs.map +0 -1
  130. package/dist/index-FnP8WtfG.js +0 -4571
  131. package/dist/index-FnP8WtfG.js.map +0 -1
  132. package/dist/quantity-stepper-B8kX8GbN.js +0 -209
  133. package/dist/quantity-stepper-B8kX8GbN.js.map +0 -1
  134. package/dist/quantity-stepper-UbAp53Ow.cjs +0 -2
  135. package/dist/quantity-stepper-UbAp53Ow.cjs.map +0 -1
  136. package/dist/schemas-DHzfUzwA.cjs +0 -86
  137. package/dist/schemas-DHzfUzwA.cjs.map +0 -1
  138. package/dist/schemas-yF4IOEUi.js.map +0 -1
@@ -1,209 +0,0 @@
1
- const y = /* @__PURE__ */ new Set([
2
- "p",
3
- "br",
4
- "a",
5
- "strong",
6
- "b",
7
- "em",
8
- "i",
9
- "u",
10
- "ul",
11
- "ol",
12
- "li",
13
- "h1",
14
- "h2",
15
- "h3",
16
- "h4",
17
- "h5",
18
- "h6",
19
- "span",
20
- "div",
21
- "table",
22
- "thead",
23
- "tbody",
24
- "tr",
25
- "th",
26
- "td",
27
- "hr",
28
- "code",
29
- "pre",
30
- "blockquote",
31
- "img",
32
- "sup",
33
- "sub"
34
- ]), S = /* @__PURE__ */ new Set([
35
- "script",
36
- "iframe",
37
- "object",
38
- "embed",
39
- "form",
40
- "input",
41
- "textarea",
42
- "select",
43
- "button",
44
- "style",
45
- "link",
46
- "meta"
47
- ]), b = {
48
- "*": /* @__PURE__ */ new Set(["class"]),
49
- a: /* @__PURE__ */ new Set(["href", "target", "rel"]),
50
- img: /* @__PURE__ */ new Set(["src", "alt", "width", "height"]),
51
- div: /* @__PURE__ */ new Set(["style"]),
52
- span: /* @__PURE__ */ new Set(["style"]),
53
- p: /* @__PURE__ */ new Set(["style"])
54
- };
55
- function A(t) {
56
- return /^\s*javascript\s*:/i.test(t);
57
- }
58
- function p(t, i) {
59
- if (t.nodeType === Node.TEXT_NODE) return;
60
- if (t.nodeType !== Node.ELEMENT_NODE) {
61
- t.parentNode?.removeChild(t);
62
- return;
63
- }
64
- const e = t, r = e.tagName.toLowerCase();
65
- if (S.has(r)) {
66
- e.parentNode?.removeChild(e);
67
- return;
68
- }
69
- if (!y.has(r)) {
70
- const n = Array.from(e.childNodes);
71
- for (const a of n)
72
- i.insertBefore(a, e);
73
- i.removeChild(e);
74
- for (const a of n)
75
- p(a, i);
76
- return;
77
- }
78
- const c = b["*"] ?? /* @__PURE__ */ new Set(), l = b[r] ?? /* @__PURE__ */ new Set(), u = Array.from(e.attributes);
79
- for (const n of u) {
80
- const a = n.name.toLowerCase();
81
- if (!c.has(a) && !l.has(a)) {
82
- e.removeAttribute(n.name);
83
- continue;
84
- }
85
- if (A(n.value)) {
86
- e.removeAttribute(n.name);
87
- continue;
88
- }
89
- }
90
- if (r === "a") {
91
- const n = e.getAttribute("href");
92
- if (n !== null) {
93
- const a = n.trim().toLowerCase();
94
- !a.startsWith("http://") && !a.startsWith("https://") && !a.startsWith("mailto:") && e.removeAttribute("href");
95
- }
96
- e.setAttribute("target", "_blank"), e.setAttribute("rel", "noopener noreferrer");
97
- }
98
- if (r === "img") {
99
- const n = e.getAttribute("src");
100
- n !== null && (n.trim().toLowerCase().startsWith("https://") || e.removeAttribute("src"));
101
- }
102
- const o = Array.from(e.childNodes);
103
- for (const n of o)
104
- p(n, e);
105
- }
106
- const g = ["http:", "https:"];
107
- function x(t) {
108
- try {
109
- return g.includes(new URL(t).protocol);
110
- } catch {
111
- return !1;
112
- }
113
- }
114
- function w(t) {
115
- if (t.startsWith("/") && !t.startsWith("//")) return !0;
116
- try {
117
- const i = new URL(t);
118
- return g.includes(i.protocol);
119
- } catch {
120
- return !1;
121
- }
122
- }
123
- function v(t, i, e) {
124
- (i === "href" || i === "src") && !w(e) || t.setAttribute(i, e);
125
- }
126
- function N(t) {
127
- if (!t) return "";
128
- const e = new DOMParser().parseFromString(t, "text/html").body, r = Array.from(e.childNodes);
129
- for (const c of r)
130
- p(c, e);
131
- return e.innerHTML;
132
- }
133
- const E = {
134
- currencySymbol: "TL",
135
- currencyPosition: "suffix",
136
- thousandsSeparator: ".",
137
- decimalSeparator: ",",
138
- alwaysShowDecimals: !1
139
- };
140
- function C(t, i) {
141
- const e = Number(t);
142
- if (!Number.isFinite(e) || e < 0) return t;
143
- const r = { ...E, ...i }, l = e % 1 !== 0 || r.alwaysShowDecimals ? e.toFixed(2) : e.toFixed(0), u = l.indexOf("."), o = u === -1 ? l : l.slice(0, u), n = u === -1 ? void 0 : l.slice(u + 1), a = o.replace(/\B(?=(\d{3})+(?!\d))/g, r.thousandsSeparator);
144
- let s;
145
- return n !== void 0 ? s = `${a}${r.decimalSeparator}${n}` : s = a, r.currencySymbol ? r.currencyPosition === "prefix" ? `${r.currencySymbol}${s}` : `${s} ${r.currencySymbol}` : s;
146
- }
147
- function L(t) {
148
- return Number.isFinite(t) ? Math.max(0, Math.min(5, t)) : 0;
149
- }
150
- function M(t) {
151
- return Number.isFinite(t) ? Math.max(0, Math.min(100, Math.round(t))) : 0;
152
- }
153
- function T(t, i = !0) {
154
- const e = L(t);
155
- if (i) {
156
- const c = Math.floor(e), l = e - c >= 0.5 ? 1 : 0, u = 5 - c - l;
157
- return "★".repeat(c) + (l ? "½" : "") + "☆".repeat(u);
158
- }
159
- const r = Math.round(e);
160
- return "★".repeat(r) + "☆".repeat(5 - r);
161
- }
162
- function D(t) {
163
- t.addEventListener(
164
- "error",
165
- () => {
166
- t.style.display = "none";
167
- },
168
- { once: !0 }
169
- );
170
- }
171
- function P(t) {
172
- const i = t.min ?? 1, e = t.max ?? 99, r = Math.min(i, e), c = Math.max(i, e), l = Math.max(r, Math.min(c, t.initial ?? r)), u = t.compact ?? !1;
173
- let o = l;
174
- const n = document.createElement("div");
175
- n.className = `gengage-qty-stepper${u ? " gengage-qty-stepper--compact" : ""}`;
176
- const a = document.createElement("button");
177
- a.className = "gengage-qty-btn", a.type = "button", a.textContent = t.decreaseSymbol ?? "−", a.setAttribute("aria-label", t.decreaseLabel ?? "Azalt");
178
- const s = document.createElement("span");
179
- s.className = "gengage-qty-value", s.textContent = String(o), s.setAttribute("aria-live", "polite"), s.setAttribute("aria-atomic", "true");
180
- const d = document.createElement("button");
181
- d.className = "gengage-qty-btn", d.type = "button", d.textContent = t.increaseSymbol ?? "+", d.setAttribute("aria-label", t.increaseLabel ?? "Artır");
182
- const m = document.createElement("button");
183
- m.className = "gengage-qty-submit", m.type = "button", u ? (m.textContent = t.submitIcon ?? "🛒", m.title = t.label ?? "Sepete Ekle") : m.textContent = t.label ?? "Sepete Ekle";
184
- function h() {
185
- a.disabled = o <= r, d.disabled = o >= c;
186
- }
187
- return a.addEventListener("click", (f) => {
188
- f.stopPropagation(), o > r && (o--, s.textContent = String(o), h());
189
- }), d.addEventListener("click", (f) => {
190
- f.stopPropagation(), o < c && (o++, s.textContent = String(o), h());
191
- }), m.addEventListener("click", (f) => {
192
- f.stopPropagation(), t.onSubmit(o);
193
- }), n.addEventListener("click", (f) => {
194
- f.stopPropagation();
195
- }), h(), n.appendChild(a), n.appendChild(s), n.appendChild(d), n.appendChild(m), n;
196
- }
197
- export {
198
- D as a,
199
- P as b,
200
- M as c,
201
- v as d,
202
- w as e,
203
- C as f,
204
- L as g,
205
- x as i,
206
- T as r,
207
- N as s
208
- };
209
- //# sourceMappingURL=quantity-stepper-B8kX8GbN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"quantity-stepper-B8kX8GbN.js","sources":["../src/common/safe-html.ts","../src/common/price-formatter.ts","../src/common/product-utils.ts","../src/common/quantity-stepper.ts"],"sourcesContent":["/**\n * DOMParser-based HTML sanitizer.\n *\n * Backend sends HTML in assistant messages (e.g. KVKK notice).\n * This module strips dangerous elements/attributes while preserving\n * safe formatting tags.\n *\n * WARNING: Any new injection point that uses innerHTML must call this function first.\n */\n\nconst ALLOWED_TAGS = new Set([\n 'p',\n 'br',\n 'a',\n 'strong',\n 'b',\n 'em',\n 'i',\n 'u',\n 'ul',\n 'ol',\n 'li',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'span',\n 'div',\n 'table',\n 'thead',\n 'tbody',\n 'tr',\n 'th',\n 'td',\n 'hr',\n 'code',\n 'pre',\n 'blockquote',\n 'img',\n 'sup',\n 'sub',\n]);\n\n/** Elements removed entirely (children NOT promoted). */\nconst DISALLOWED_TAGS = new Set([\n 'script',\n 'iframe',\n 'object',\n 'embed',\n 'form',\n 'input',\n 'textarea',\n 'select',\n 'button',\n 'style',\n 'link',\n 'meta',\n]);\n\n/**\n * Attributes allowed per-tag. `'*'` means any allowed tag.\n *\n * KNOWN TECH DEBT [SECURITY]: `style` attribute on div/span/p enables CSS-based XSS vectors:\n * - `style=\"background:url(javascript:alert(1))\"` bypasses hasJavascriptProtocol\n * (which checks the raw attribute value, not nested CSS url() arguments)\n * - `style=\"expression(alert(1))\"` on legacy IE\n * - `style=\"-moz-binding:url(...)\"` on older Firefox\n * Tracked for fix: either remove `style` from ALLOWED_ATTRS entirely, or implement\n * a CSS property sanitizer that allowlists specific properties and validates values.\n * Also add `<template>` to DISALLOWED_TAGS for defense-in-depth.\n * Risk is mitigated by: (1) modern browsers block javascript: in CSS url(),\n * (2) backend is trusted first-party, (3) only relevant if backend is compromised.\n */\nconst ALLOWED_ATTRS: Record<string, Set<string>> = {\n '*': new Set(['class']),\n a: new Set(['href', 'target', 'rel']),\n img: new Set(['src', 'alt', 'width', 'height']),\n div: new Set(['style']),\n span: new Set(['style']),\n p: new Set(['style']),\n};\n\nfunction hasJavascriptProtocol(value: string): boolean {\n // Normalize whitespace + case then check\n return /^\\s*javascript\\s*:/i.test(value);\n}\n\nfunction sanitizeNode(node: Node, parent: Node): void {\n if (node.nodeType === Node.TEXT_NODE) return;\n\n if (node.nodeType !== Node.ELEMENT_NODE) {\n node.parentNode?.removeChild(node);\n return;\n }\n\n const el = node as Element;\n const tag = el.tagName.toLowerCase();\n\n // Disallowed: remove entirely (including children)\n if (DISALLOWED_TAGS.has(tag)) {\n el.parentNode?.removeChild(el);\n return;\n }\n\n // Unknown: unwrap (promote children to parent)\n if (!ALLOWED_TAGS.has(tag)) {\n const children = Array.from(el.childNodes);\n for (const child of children) {\n parent.insertBefore(child, el);\n }\n parent.removeChild(el);\n // Sanitize promoted children\n for (const child of children) {\n sanitizeNode(child, parent);\n }\n return;\n }\n\n // Sanitize attributes\n const globalAllowed = ALLOWED_ATTRS['*'] ?? new Set();\n const tagAllowed = ALLOWED_ATTRS[tag] ?? new Set();\n const attrs = Array.from(el.attributes);\n\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n\n if (!globalAllowed.has(name) && !tagAllowed.has(name)) {\n el.removeAttribute(attr.name);\n continue;\n }\n\n // Strip javascript: protocol from any attribute value\n if (hasJavascriptProtocol(attr.value)) {\n el.removeAttribute(attr.name);\n continue;\n }\n }\n\n // Validate specific attribute values\n if (tag === 'a') {\n const href = el.getAttribute('href');\n if (href !== null) {\n const trimmed = href.trim().toLowerCase();\n if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://') && !trimmed.startsWith('mailto:')) {\n el.removeAttribute('href');\n }\n }\n // Force safe link behavior\n el.setAttribute('target', '_blank');\n el.setAttribute('rel', 'noopener noreferrer');\n }\n\n if (tag === 'img') {\n const src = el.getAttribute('src');\n if (src !== null) {\n const trimmed = src.trim().toLowerCase();\n if (!trimmed.startsWith('https://')) {\n el.removeAttribute('src');\n }\n }\n }\n\n // Recurse into children (snapshot the list since we may mutate)\n const children = Array.from(el.childNodes);\n for (const child of children) {\n sanitizeNode(child, el);\n }\n}\n\n/**\n * Sanitize an HTML string for safe insertion via innerHTML.\n *\n * - Allowed tags are preserved; disallowed tags are removed entirely.\n * - Unknown tags are unwrapped (children promoted).\n * - `<a>` tags are forced to `target=\"_blank\" rel=\"noopener noreferrer\"`.\n * - Only `https://` is allowed for `<img src>`.\n */\nconst SAFE_URL_PROTOCOLS = ['http:', 'https:'];\n\n/** Check if a URL uses a safe protocol (http or https). */\nexport function isSafeImageUrl(url: string): boolean {\n try {\n return SAFE_URL_PROTOCOLS.includes(new URL(url).protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a URL is safe for use in href/src attributes.\n * Allows http:, https:, and relative paths (starting with `/`).\n */\nexport function isSafeUrl(url: string): boolean {\n // Allow relative paths but reject protocol-relative URLs (//evil.com/...)\n if (url.startsWith('/') && !url.startsWith('//')) return true;\n try {\n const parsed = new URL(url);\n return SAFE_URL_PROTOCOLS.includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Safely set an attribute on an element.\n * For `href` and `src` attributes, validates the URL against safe protocols.\n */\nexport function safeSetAttribute(el: HTMLElement, attr: string, value: string): void {\n if (attr === 'href' || attr === 'src') {\n if (!isSafeUrl(value)) return;\n }\n el.setAttribute(attr, value);\n}\n\nexport function sanitizeHtml(raw: string): string {\n if (!raw) return '';\n\n const doc = new DOMParser().parseFromString(raw, 'text/html');\n const body = doc.body;\n\n const children = Array.from(body.childNodes);\n for (const child of children) {\n sanitizeNode(child, body);\n }\n\n return body.innerHTML;\n}\n","/**\n * Configurable price formatting.\n *\n * Defaults to Turkish locale (dot thousands, comma decimal, TL suffix).\n * Configure via widget config `pricing` field for any locale/currency.\n */\n\nexport interface PriceFormatConfig {\n /** Currency symbol. Default: 'TL' */\n currencySymbol?: string;\n /** Where to place the symbol. Default: 'suffix' */\n currencyPosition?: 'prefix' | 'suffix';\n /** Separator between thousands. Default: '.' (Turkish) */\n thousandsSeparator?: string;\n /** Decimal point character. Default: ',' (Turkish) */\n decimalSeparator?: string;\n /** Whether to show decimal part for whole numbers. Default: false */\n alwaysShowDecimals?: boolean;\n}\n\nconst TURKISH_DEFAULTS: Required<PriceFormatConfig> = {\n currencySymbol: 'TL',\n currencyPosition: 'suffix',\n thousandsSeparator: '.',\n decimalSeparator: ',',\n alwaysShowDecimals: false,\n};\n\n/**\n * Formats a raw numeric price string into the configured locale format.\n *\n * Examples (default Turkish):\n * \"17990\" → \"17.990 TL\"\n * \"17990.5\" → \"17.990,50 TL\"\n *\n * Examples (GBP prefix):\n * \"17990\" with { currencySymbol: '£', currencyPosition: 'prefix', thousandsSeparator: ',', decimalSeparator: '.' }\n * → \"£17,990\"\n *\n * Returns the input as-is if it's not a valid number.\n */\nexport function formatPrice(raw: string, config?: PriceFormatConfig): string {\n const num = Number(raw);\n if (!Number.isFinite(num) || num < 0) return raw;\n\n const resolved = { ...TURKISH_DEFAULTS, ...config };\n\n const hasDecimals = num % 1 !== 0;\n const fixed = hasDecimals || resolved.alwaysShowDecimals ? num.toFixed(2) : num.toFixed(0);\n const dotIdx = fixed.indexOf('.');\n const intPart = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx);\n const decPart = dotIdx === -1 ? undefined : fixed.slice(dotIdx + 1);\n\n // Add thousands separators to integer part\n const withSeparators = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, resolved.thousandsSeparator);\n\n let formatted: string;\n if (decPart !== undefined) {\n formatted = `${withSeparators}${resolved.decimalSeparator}${decPart}`;\n } else {\n formatted = withSeparators;\n }\n\n if (resolved.currencySymbol) {\n if (resolved.currencyPosition === 'prefix') {\n return `${resolved.currencySymbol}${formatted}`;\n }\n return `${formatted} ${resolved.currencySymbol}`;\n }\n\n return formatted;\n}\n","/**\n * Shared product rendering utilities.\n *\n * Extracted from chat/renderUISpec and simrel/ProductCard to eliminate\n * duplication and provide consistent behavior across all widgets.\n */\n\n/** Clamp a rating value to the 0–5 range. Returns 0 for NaN/non-finite. */\nexport function clampRating(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(5, value));\n}\n\n/** Clamp a discount percentage to the 0–100 range, rounded to integer. Returns 0 for NaN/non-finite. */\nexport function clampDiscount(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(100, Math.round(value)));\n}\n\n/**\n * Render a star rating string with full, half, and empty stars.\n *\n * @param rating - A numeric rating (will be clamped to 0–5).\n * @param halfStars - Whether to render half-star characters. Defaults to true.\n * @returns A string like \"★★★½☆\" or \"★★★☆☆\" (without half-stars).\n */\nexport function renderStarRating(rating: number, halfStars: boolean = true): string {\n const clamped = clampRating(rating);\n if (halfStars) {\n const full = Math.floor(clamped);\n const half = clamped - full >= 0.5 ? 1 : 0;\n const empty = 5 - full - half;\n return '\\u2605'.repeat(full) + (half ? '\\u00BD' : '') + '\\u2606'.repeat(empty);\n }\n const rounded = Math.round(clamped);\n return '\\u2605'.repeat(rounded) + '\\u2606'.repeat(5 - rounded);\n}\n\n/**\n * Attach a one-time error handler that hides the image on load failure.\n *\n * Works with any HTMLImageElement. Hides the element by setting\n * `display: none` so layout doesn't break from broken images.\n */\nexport function addImageErrorHandler(img: HTMLImageElement): void {\n img.addEventListener(\n 'error',\n () => {\n img.style.display = 'none';\n },\n { once: true },\n );\n}\n","export interface QuantityStepperOptions {\n min?: number;\n max?: number;\n initial?: number;\n label?: string;\n compact?: boolean;\n decreaseLabel?: string;\n increaseLabel?: string;\n /** Symbol for decrease button (default: '\\u2212' minus sign). */\n decreaseSymbol?: string;\n /** Symbol for increase button (default: '+'). */\n increaseSymbol?: string;\n /** Icon/text for compact mode submit button (default: shopping cart emoji). */\n submitIcon?: string;\n onSubmit: (quantity: number) => void;\n}\n\n/**\n * Creates a quantity stepper with [−] [value] [+] and a submit button.\n * Compact mode renders a cart icon button; full mode renders a labeled button.\n */\nexport function createQuantityStepper(options: QuantityStepperOptions): HTMLElement {\n const rawMin = options.min ?? 1;\n const rawMax = options.max ?? 99;\n // Ensure min <= max; swap if caller provided inverted range\n const min = Math.min(rawMin, rawMax);\n const max = Math.max(rawMin, rawMax);\n const initial = Math.max(min, Math.min(max, options.initial ?? min));\n const compact = options.compact ?? false;\n\n let quantity = initial;\n\n const container = document.createElement('div');\n container.className = `gengage-qty-stepper${compact ? ' gengage-qty-stepper--compact' : ''}`;\n\n const decBtn = document.createElement('button');\n decBtn.className = 'gengage-qty-btn';\n decBtn.type = 'button';\n decBtn.textContent = options.decreaseSymbol ?? '\\u2212'; // minus sign\n decBtn.setAttribute('aria-label', options.decreaseLabel ?? 'Azalt');\n\n const valueEl = document.createElement('span');\n valueEl.className = 'gengage-qty-value';\n valueEl.textContent = String(quantity);\n valueEl.setAttribute('aria-live', 'polite');\n valueEl.setAttribute('aria-atomic', 'true');\n\n const incBtn = document.createElement('button');\n incBtn.className = 'gengage-qty-btn';\n incBtn.type = 'button';\n incBtn.textContent = options.increaseSymbol ?? '+';\n incBtn.setAttribute('aria-label', options.increaseLabel ?? 'Art\\u0131r');\n\n const submitBtn = document.createElement('button');\n submitBtn.className = 'gengage-qty-submit';\n submitBtn.type = 'button';\n\n if (compact) {\n submitBtn.textContent = options.submitIcon ?? '\\uD83D\\uDED2'; // shopping cart emoji\n submitBtn.title = options.label ?? 'Sepete Ekle';\n } else {\n submitBtn.textContent = options.label ?? 'Sepete Ekle';\n }\n\n function updateButtons(): void {\n decBtn.disabled = quantity <= min;\n incBtn.disabled = quantity >= max;\n }\n\n decBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (quantity > min) {\n quantity--;\n valueEl.textContent = String(quantity);\n updateButtons();\n }\n });\n\n incBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (quantity < max) {\n quantity++;\n valueEl.textContent = String(quantity);\n updateButtons();\n }\n });\n\n submitBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n options.onSubmit(quantity);\n });\n\n // Prevent card click when interacting with stepper\n container.addEventListener('click', (e) => {\n e.stopPropagation();\n });\n\n updateButtons();\n\n container.appendChild(decBtn);\n container.appendChild(valueEl);\n container.appendChild(incBtn);\n container.appendChild(submitBtn);\n\n return container;\n}\n"],"names":["ALLOWED_TAGS","DISALLOWED_TAGS","ALLOWED_ATTRS","hasJavascriptProtocol","value","sanitizeNode","node","parent","el","tag","children","child","globalAllowed","tagAllowed","attrs","attr","name","href","trimmed","src","SAFE_URL_PROTOCOLS","isSafeImageUrl","url","isSafeUrl","parsed","safeSetAttribute","sanitizeHtml","raw","body","TURKISH_DEFAULTS","formatPrice","config","num","resolved","fixed","dotIdx","intPart","decPart","withSeparators","formatted","clampRating","clampDiscount","renderStarRating","rating","halfStars","clamped","full","half","empty","rounded","addImageErrorHandler","img","createQuantityStepper","options","rawMin","rawMax","min","max","initial","compact","quantity","container","decBtn","valueEl","incBtn","submitBtn","updateButtons","e"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- "use strict";const A=new Set(["p","br","a","strong","b","em","i","u","ul","ol","li","h1","h2","h3","h4","h5","h6","span","div","table","thead","tbody","tr","th","td","hr","code","pre","blockquote","img","sup","sub"]),E=new Set(["script","iframe","object","embed","form","input","textarea","select","button","style","link","meta"]),b={"*":new Set(["class"]),a:new Set(["href","target","rel"]),img:new Set(["src","alt","width","height"]),div:new Set(["style"]),span:new Set(["style"]),p:new Set(["style"])};function w(t){return/^\s*javascript\s*:/i.test(t)}function p(t,i){if(t.nodeType===Node.TEXT_NODE)return;if(t.nodeType!==Node.ELEMENT_NODE){t.parentNode?.removeChild(t);return}const e=t,n=e.tagName.toLowerCase();if(E.has(n)){e.parentNode?.removeChild(e);return}if(!A.has(n)){const r=Array.from(e.childNodes);for(const a of r)i.insertBefore(a,e);i.removeChild(e);for(const a of r)p(a,i);return}const c=b["*"]??new Set,l=b[n]??new Set,u=Array.from(e.attributes);for(const r of u){const a=r.name.toLowerCase();if(!c.has(a)&&!l.has(a)){e.removeAttribute(r.name);continue}if(w(r.value)){e.removeAttribute(r.name);continue}}if(n==="a"){const r=e.getAttribute("href");if(r!==null){const a=r.trim().toLowerCase();!a.startsWith("http://")&&!a.startsWith("https://")&&!a.startsWith("mailto:")&&e.removeAttribute("href")}e.setAttribute("target","_blank"),e.setAttribute("rel","noopener noreferrer")}if(n==="img"){const r=e.getAttribute("src");r!==null&&(r.trim().toLowerCase().startsWith("https://")||e.removeAttribute("src"))}const o=Array.from(e.childNodes);for(const r of o)p(r,e)}const S=["http:","https:"];function L(t){try{return S.includes(new URL(t).protocol)}catch{return!1}}function g(t){if(t.startsWith("/")&&!t.startsWith("//"))return!0;try{const i=new URL(t);return S.includes(i.protocol)}catch{return!1}}function x(t,i,e){(i==="href"||i==="src")&&!g(e)||t.setAttribute(i,e)}function v(t){if(!t)return"";const e=new DOMParser().parseFromString(t,"text/html").body,n=Array.from(e.childNodes);for(const c of n)p(c,e);return e.innerHTML}const N={currencySymbol:"TL",currencyPosition:"suffix",thousandsSeparator:".",decimalSeparator:",",alwaysShowDecimals:!1};function C(t,i){const e=Number(t);if(!Number.isFinite(e)||e<0)return t;const n={...N,...i},l=e%1!==0||n.alwaysShowDecimals?e.toFixed(2):e.toFixed(0),u=l.indexOf("."),o=u===-1?l:l.slice(0,u),r=u===-1?void 0:l.slice(u+1),a=o.replace(/\B(?=(\d{3})+(?!\d))/g,n.thousandsSeparator);let s;return r!==void 0?s=`${a}${n.decimalSeparator}${r}`:s=a,n.currencySymbol?n.currencyPosition==="prefix"?`${n.currencySymbol}${s}`:`${s} ${n.currencySymbol}`:s}function y(t){return Number.isFinite(t)?Math.max(0,Math.min(5,t)):0}function M(t){return Number.isFinite(t)?Math.max(0,Math.min(100,Math.round(t))):0}function D(t,i=!0){const e=y(t);if(i){const c=Math.floor(e),l=e-c>=.5?1:0,u=5-c-l;return"★".repeat(c)+(l?"½":"")+"☆".repeat(u)}const n=Math.round(e);return"★".repeat(n)+"☆".repeat(5-n)}function T(t){t.addEventListener("error",()=>{t.style.display="none"},{once:!0})}function P(t){const i=t.min??1,e=t.max??99,n=Math.min(i,e),c=Math.max(i,e),l=Math.max(n,Math.min(c,t.initial??n)),u=t.compact??!1;let o=l;const r=document.createElement("div");r.className=`gengage-qty-stepper${u?" gengage-qty-stepper--compact":""}`;const a=document.createElement("button");a.className="gengage-qty-btn",a.type="button",a.textContent=t.decreaseSymbol??"−",a.setAttribute("aria-label",t.decreaseLabel??"Azalt");const s=document.createElement("span");s.className="gengage-qty-value",s.textContent=String(o),s.setAttribute("aria-live","polite"),s.setAttribute("aria-atomic","true");const d=document.createElement("button");d.className="gengage-qty-btn",d.type="button",d.textContent=t.increaseSymbol??"+",d.setAttribute("aria-label",t.increaseLabel??"Artır");const m=document.createElement("button");m.className="gengage-qty-submit",m.type="button",u?(m.textContent=t.submitIcon??"🛒",m.title=t.label??"Sepete Ekle"):m.textContent=t.label??"Sepete Ekle";function h(){a.disabled=o<=n,d.disabled=o>=c}return a.addEventListener("click",f=>{f.stopPropagation(),o>n&&(o--,s.textContent=String(o),h())}),d.addEventListener("click",f=>{f.stopPropagation(),o<c&&(o++,s.textContent=String(o),h())}),m.addEventListener("click",f=>{f.stopPropagation(),t.onSubmit(o)}),r.addEventListener("click",f=>{f.stopPropagation()}),h(),r.appendChild(a),r.appendChild(s),r.appendChild(d),r.appendChild(m),r}exports.addImageErrorHandler=T;exports.clampDiscount=M;exports.clampRating=y;exports.createQuantityStepper=P;exports.formatPrice=C;exports.isSafeImageUrl=L;exports.isSafeUrl=g;exports.renderStarRating=D;exports.safeSetAttribute=x;exports.sanitizeHtml=v;
2
- //# sourceMappingURL=quantity-stepper-UbAp53Ow.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"quantity-stepper-UbAp53Ow.cjs","sources":["../src/common/safe-html.ts","../src/common/price-formatter.ts","../src/common/product-utils.ts","../src/common/quantity-stepper.ts"],"sourcesContent":["/**\n * DOMParser-based HTML sanitizer.\n *\n * Backend sends HTML in assistant messages (e.g. KVKK notice).\n * This module strips dangerous elements/attributes while preserving\n * safe formatting tags.\n *\n * WARNING: Any new injection point that uses innerHTML must call this function first.\n */\n\nconst ALLOWED_TAGS = new Set([\n 'p',\n 'br',\n 'a',\n 'strong',\n 'b',\n 'em',\n 'i',\n 'u',\n 'ul',\n 'ol',\n 'li',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'span',\n 'div',\n 'table',\n 'thead',\n 'tbody',\n 'tr',\n 'th',\n 'td',\n 'hr',\n 'code',\n 'pre',\n 'blockquote',\n 'img',\n 'sup',\n 'sub',\n]);\n\n/** Elements removed entirely (children NOT promoted). */\nconst DISALLOWED_TAGS = new Set([\n 'script',\n 'iframe',\n 'object',\n 'embed',\n 'form',\n 'input',\n 'textarea',\n 'select',\n 'button',\n 'style',\n 'link',\n 'meta',\n]);\n\n/**\n * Attributes allowed per-tag. `'*'` means any allowed tag.\n *\n * KNOWN TECH DEBT [SECURITY]: `style` attribute on div/span/p enables CSS-based XSS vectors:\n * - `style=\"background:url(javascript:alert(1))\"` bypasses hasJavascriptProtocol\n * (which checks the raw attribute value, not nested CSS url() arguments)\n * - `style=\"expression(alert(1))\"` on legacy IE\n * - `style=\"-moz-binding:url(...)\"` on older Firefox\n * Tracked for fix: either remove `style` from ALLOWED_ATTRS entirely, or implement\n * a CSS property sanitizer that allowlists specific properties and validates values.\n * Also add `<template>` to DISALLOWED_TAGS for defense-in-depth.\n * Risk is mitigated by: (1) modern browsers block javascript: in CSS url(),\n * (2) backend is trusted first-party, (3) only relevant if backend is compromised.\n */\nconst ALLOWED_ATTRS: Record<string, Set<string>> = {\n '*': new Set(['class']),\n a: new Set(['href', 'target', 'rel']),\n img: new Set(['src', 'alt', 'width', 'height']),\n div: new Set(['style']),\n span: new Set(['style']),\n p: new Set(['style']),\n};\n\nfunction hasJavascriptProtocol(value: string): boolean {\n // Normalize whitespace + case then check\n return /^\\s*javascript\\s*:/i.test(value);\n}\n\nfunction sanitizeNode(node: Node, parent: Node): void {\n if (node.nodeType === Node.TEXT_NODE) return;\n\n if (node.nodeType !== Node.ELEMENT_NODE) {\n node.parentNode?.removeChild(node);\n return;\n }\n\n const el = node as Element;\n const tag = el.tagName.toLowerCase();\n\n // Disallowed: remove entirely (including children)\n if (DISALLOWED_TAGS.has(tag)) {\n el.parentNode?.removeChild(el);\n return;\n }\n\n // Unknown: unwrap (promote children to parent)\n if (!ALLOWED_TAGS.has(tag)) {\n const children = Array.from(el.childNodes);\n for (const child of children) {\n parent.insertBefore(child, el);\n }\n parent.removeChild(el);\n // Sanitize promoted children\n for (const child of children) {\n sanitizeNode(child, parent);\n }\n return;\n }\n\n // Sanitize attributes\n const globalAllowed = ALLOWED_ATTRS['*'] ?? new Set();\n const tagAllowed = ALLOWED_ATTRS[tag] ?? new Set();\n const attrs = Array.from(el.attributes);\n\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n\n if (!globalAllowed.has(name) && !tagAllowed.has(name)) {\n el.removeAttribute(attr.name);\n continue;\n }\n\n // Strip javascript: protocol from any attribute value\n if (hasJavascriptProtocol(attr.value)) {\n el.removeAttribute(attr.name);\n continue;\n }\n }\n\n // Validate specific attribute values\n if (tag === 'a') {\n const href = el.getAttribute('href');\n if (href !== null) {\n const trimmed = href.trim().toLowerCase();\n if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://') && !trimmed.startsWith('mailto:')) {\n el.removeAttribute('href');\n }\n }\n // Force safe link behavior\n el.setAttribute('target', '_blank');\n el.setAttribute('rel', 'noopener noreferrer');\n }\n\n if (tag === 'img') {\n const src = el.getAttribute('src');\n if (src !== null) {\n const trimmed = src.trim().toLowerCase();\n if (!trimmed.startsWith('https://')) {\n el.removeAttribute('src');\n }\n }\n }\n\n // Recurse into children (snapshot the list since we may mutate)\n const children = Array.from(el.childNodes);\n for (const child of children) {\n sanitizeNode(child, el);\n }\n}\n\n/**\n * Sanitize an HTML string for safe insertion via innerHTML.\n *\n * - Allowed tags are preserved; disallowed tags are removed entirely.\n * - Unknown tags are unwrapped (children promoted).\n * - `<a>` tags are forced to `target=\"_blank\" rel=\"noopener noreferrer\"`.\n * - Only `https://` is allowed for `<img src>`.\n */\nconst SAFE_URL_PROTOCOLS = ['http:', 'https:'];\n\n/** Check if a URL uses a safe protocol (http or https). */\nexport function isSafeImageUrl(url: string): boolean {\n try {\n return SAFE_URL_PROTOCOLS.includes(new URL(url).protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a URL is safe for use in href/src attributes.\n * Allows http:, https:, and relative paths (starting with `/`).\n */\nexport function isSafeUrl(url: string): boolean {\n // Allow relative paths but reject protocol-relative URLs (//evil.com/...)\n if (url.startsWith('/') && !url.startsWith('//')) return true;\n try {\n const parsed = new URL(url);\n return SAFE_URL_PROTOCOLS.includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Safely set an attribute on an element.\n * For `href` and `src` attributes, validates the URL against safe protocols.\n */\nexport function safeSetAttribute(el: HTMLElement, attr: string, value: string): void {\n if (attr === 'href' || attr === 'src') {\n if (!isSafeUrl(value)) return;\n }\n el.setAttribute(attr, value);\n}\n\nexport function sanitizeHtml(raw: string): string {\n if (!raw) return '';\n\n const doc = new DOMParser().parseFromString(raw, 'text/html');\n const body = doc.body;\n\n const children = Array.from(body.childNodes);\n for (const child of children) {\n sanitizeNode(child, body);\n }\n\n return body.innerHTML;\n}\n","/**\n * Configurable price formatting.\n *\n * Defaults to Turkish locale (dot thousands, comma decimal, TL suffix).\n * Configure via widget config `pricing` field for any locale/currency.\n */\n\nexport interface PriceFormatConfig {\n /** Currency symbol. Default: 'TL' */\n currencySymbol?: string;\n /** Where to place the symbol. Default: 'suffix' */\n currencyPosition?: 'prefix' | 'suffix';\n /** Separator between thousands. Default: '.' (Turkish) */\n thousandsSeparator?: string;\n /** Decimal point character. Default: ',' (Turkish) */\n decimalSeparator?: string;\n /** Whether to show decimal part for whole numbers. Default: false */\n alwaysShowDecimals?: boolean;\n}\n\nconst TURKISH_DEFAULTS: Required<PriceFormatConfig> = {\n currencySymbol: 'TL',\n currencyPosition: 'suffix',\n thousandsSeparator: '.',\n decimalSeparator: ',',\n alwaysShowDecimals: false,\n};\n\n/**\n * Formats a raw numeric price string into the configured locale format.\n *\n * Examples (default Turkish):\n * \"17990\" → \"17.990 TL\"\n * \"17990.5\" → \"17.990,50 TL\"\n *\n * Examples (GBP prefix):\n * \"17990\" with { currencySymbol: '£', currencyPosition: 'prefix', thousandsSeparator: ',', decimalSeparator: '.' }\n * → \"£17,990\"\n *\n * Returns the input as-is if it's not a valid number.\n */\nexport function formatPrice(raw: string, config?: PriceFormatConfig): string {\n const num = Number(raw);\n if (!Number.isFinite(num) || num < 0) return raw;\n\n const resolved = { ...TURKISH_DEFAULTS, ...config };\n\n const hasDecimals = num % 1 !== 0;\n const fixed = hasDecimals || resolved.alwaysShowDecimals ? num.toFixed(2) : num.toFixed(0);\n const dotIdx = fixed.indexOf('.');\n const intPart = dotIdx === -1 ? fixed : fixed.slice(0, dotIdx);\n const decPart = dotIdx === -1 ? undefined : fixed.slice(dotIdx + 1);\n\n // Add thousands separators to integer part\n const withSeparators = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, resolved.thousandsSeparator);\n\n let formatted: string;\n if (decPart !== undefined) {\n formatted = `${withSeparators}${resolved.decimalSeparator}${decPart}`;\n } else {\n formatted = withSeparators;\n }\n\n if (resolved.currencySymbol) {\n if (resolved.currencyPosition === 'prefix') {\n return `${resolved.currencySymbol}${formatted}`;\n }\n return `${formatted} ${resolved.currencySymbol}`;\n }\n\n return formatted;\n}\n","/**\n * Shared product rendering utilities.\n *\n * Extracted from chat/renderUISpec and simrel/ProductCard to eliminate\n * duplication and provide consistent behavior across all widgets.\n */\n\n/** Clamp a rating value to the 0–5 range. Returns 0 for NaN/non-finite. */\nexport function clampRating(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(5, value));\n}\n\n/** Clamp a discount percentage to the 0–100 range, rounded to integer. Returns 0 for NaN/non-finite. */\nexport function clampDiscount(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, Math.min(100, Math.round(value)));\n}\n\n/**\n * Render a star rating string with full, half, and empty stars.\n *\n * @param rating - A numeric rating (will be clamped to 0–5).\n * @param halfStars - Whether to render half-star characters. Defaults to true.\n * @returns A string like \"★★★½☆\" or \"★★★☆☆\" (without half-stars).\n */\nexport function renderStarRating(rating: number, halfStars: boolean = true): string {\n const clamped = clampRating(rating);\n if (halfStars) {\n const full = Math.floor(clamped);\n const half = clamped - full >= 0.5 ? 1 : 0;\n const empty = 5 - full - half;\n return '\\u2605'.repeat(full) + (half ? '\\u00BD' : '') + '\\u2606'.repeat(empty);\n }\n const rounded = Math.round(clamped);\n return '\\u2605'.repeat(rounded) + '\\u2606'.repeat(5 - rounded);\n}\n\n/**\n * Attach a one-time error handler that hides the image on load failure.\n *\n * Works with any HTMLImageElement. Hides the element by setting\n * `display: none` so layout doesn't break from broken images.\n */\nexport function addImageErrorHandler(img: HTMLImageElement): void {\n img.addEventListener(\n 'error',\n () => {\n img.style.display = 'none';\n },\n { once: true },\n );\n}\n","export interface QuantityStepperOptions {\n min?: number;\n max?: number;\n initial?: number;\n label?: string;\n compact?: boolean;\n decreaseLabel?: string;\n increaseLabel?: string;\n /** Symbol for decrease button (default: '\\u2212' minus sign). */\n decreaseSymbol?: string;\n /** Symbol for increase button (default: '+'). */\n increaseSymbol?: string;\n /** Icon/text for compact mode submit button (default: shopping cart emoji). */\n submitIcon?: string;\n onSubmit: (quantity: number) => void;\n}\n\n/**\n * Creates a quantity stepper with [−] [value] [+] and a submit button.\n * Compact mode renders a cart icon button; full mode renders a labeled button.\n */\nexport function createQuantityStepper(options: QuantityStepperOptions): HTMLElement {\n const rawMin = options.min ?? 1;\n const rawMax = options.max ?? 99;\n // Ensure min <= max; swap if caller provided inverted range\n const min = Math.min(rawMin, rawMax);\n const max = Math.max(rawMin, rawMax);\n const initial = Math.max(min, Math.min(max, options.initial ?? min));\n const compact = options.compact ?? false;\n\n let quantity = initial;\n\n const container = document.createElement('div');\n container.className = `gengage-qty-stepper${compact ? ' gengage-qty-stepper--compact' : ''}`;\n\n const decBtn = document.createElement('button');\n decBtn.className = 'gengage-qty-btn';\n decBtn.type = 'button';\n decBtn.textContent = options.decreaseSymbol ?? '\\u2212'; // minus sign\n decBtn.setAttribute('aria-label', options.decreaseLabel ?? 'Azalt');\n\n const valueEl = document.createElement('span');\n valueEl.className = 'gengage-qty-value';\n valueEl.textContent = String(quantity);\n valueEl.setAttribute('aria-live', 'polite');\n valueEl.setAttribute('aria-atomic', 'true');\n\n const incBtn = document.createElement('button');\n incBtn.className = 'gengage-qty-btn';\n incBtn.type = 'button';\n incBtn.textContent = options.increaseSymbol ?? '+';\n incBtn.setAttribute('aria-label', options.increaseLabel ?? 'Art\\u0131r');\n\n const submitBtn = document.createElement('button');\n submitBtn.className = 'gengage-qty-submit';\n submitBtn.type = 'button';\n\n if (compact) {\n submitBtn.textContent = options.submitIcon ?? '\\uD83D\\uDED2'; // shopping cart emoji\n submitBtn.title = options.label ?? 'Sepete Ekle';\n } else {\n submitBtn.textContent = options.label ?? 'Sepete Ekle';\n }\n\n function updateButtons(): void {\n decBtn.disabled = quantity <= min;\n incBtn.disabled = quantity >= max;\n }\n\n decBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (quantity > min) {\n quantity--;\n valueEl.textContent = String(quantity);\n updateButtons();\n }\n });\n\n incBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (quantity < max) {\n quantity++;\n valueEl.textContent = String(quantity);\n updateButtons();\n }\n });\n\n submitBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n options.onSubmit(quantity);\n });\n\n // Prevent card click when interacting with stepper\n container.addEventListener('click', (e) => {\n e.stopPropagation();\n });\n\n updateButtons();\n\n container.appendChild(decBtn);\n container.appendChild(valueEl);\n container.appendChild(incBtn);\n container.appendChild(submitBtn);\n\n return container;\n}\n"],"names":["ALLOWED_TAGS","DISALLOWED_TAGS","ALLOWED_ATTRS","hasJavascriptProtocol","value","sanitizeNode","node","parent","el","tag","children","child","globalAllowed","tagAllowed","attrs","attr","name","href","trimmed","src","SAFE_URL_PROTOCOLS","isSafeImageUrl","url","isSafeUrl","parsed","safeSetAttribute","sanitizeHtml","raw","body","TURKISH_DEFAULTS","formatPrice","config","num","resolved","fixed","dotIdx","intPart","decPart","withSeparators","formatted","clampRating","clampDiscount","renderStarRating","rating","halfStars","clamped","full","half","empty","rounded","addImageErrorHandler","img","createQuantityStepper","options","rawMin","rawMax","min","max","initial","compact","quantity","container","decBtn","valueEl","incBtn","submitBtn","updateButtons","e"],"mappings":"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"}