@syntrologie/adapt-faq 2.16.0 → 2.18.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/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +7 -0
- package/dist/chunk-S6WIENQP.js +578 -0
- package/dist/chunk-S6WIENQP.js.map +7 -0
- package/dist/editor.d.ts +35 -33
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +4821 -308
- package/dist/editor.js.map +7 -0
- package/dist/runtime.d.ts +3 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +848 -91
- package/dist/runtime.js.map +7 -0
- package/dist/schema.d.ts +609 -77
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +444 -206
- package/dist/schema.js.map +7 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -20
- package/dist/FAQWidget.d.ts +0 -33
- package/dist/FAQWidget.d.ts.map +0 -1
- package/dist/FAQWidget.js +0 -375
- package/dist/FAQWidgetLit.js +0 -534
- package/dist/cdn.d.ts +0 -70
- package/dist/cdn.d.ts.map +0 -1
- package/dist/cdn.js +0 -46
- package/dist/editor-lit.d.ts +0 -37
- package/dist/editor-lit.d.ts.map +0 -1
- package/dist/editor-lit.js +0 -195
- package/dist/executors.js +0 -150
- package/dist/faq-styles.js +0 -204
- package/dist/faq-types.js +0 -7
- package/dist/runtime-lit.d.ts +0 -85
- package/dist/runtime-lit.d.ts.map +0 -1
- package/dist/runtime-lit.js +0 -94
- package/dist/state.js +0 -132
- package/dist/summarize.js +0 -62
- package/dist/types.js +0 -17
- package/node_modules/@syntrologie/sdk-contracts/dist/index.d.ts +0 -129
- package/node_modules/@syntrologie/sdk-contracts/dist/index.js +0 -17
- package/node_modules/@syntrologie/sdk-contracts/dist/schemas.d.ts +0 -2296
- package/node_modules/@syntrologie/sdk-contracts/dist/schemas.js +0 -361
- package/node_modules/@syntrologie/sdk-contracts/package.json +0 -33
package/dist/runtime.js
CHANGED
|
@@ -1,94 +1,851 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
import {
|
|
2
|
+
purple,
|
|
3
|
+
slateGrey
|
|
4
|
+
} from "./chunk-S6WIENQP.js";
|
|
5
|
+
import "./chunk-5WRI5ZAA.js";
|
|
6
|
+
|
|
7
|
+
// src/executors.ts
|
|
8
|
+
function resolveItem(store, itemId, itemQuestion) {
|
|
9
|
+
if (itemId) {
|
|
10
|
+
const found = store.getState().items.find((i) => i.config.id === itemId);
|
|
11
|
+
if (found) return found;
|
|
12
|
+
}
|
|
13
|
+
if (itemQuestion) {
|
|
14
|
+
const found = store.findByQuestion(itemQuestion);
|
|
15
|
+
if (found) return found;
|
|
16
|
+
}
|
|
17
|
+
throw new Error("FAQ item not found");
|
|
18
|
+
}
|
|
19
|
+
async function executeScrollToFaq(action, context, store) {
|
|
20
|
+
const item = resolveItem(store, action.itemId, action.itemQuestion);
|
|
21
|
+
const { id } = item.config;
|
|
22
|
+
if (action.expand !== false) {
|
|
23
|
+
store.expand(id);
|
|
24
|
+
}
|
|
25
|
+
const el = document.querySelector(`[data-faq-item-id="${id}"]`);
|
|
26
|
+
if (el) {
|
|
27
|
+
el.scrollIntoView({
|
|
28
|
+
behavior: action.behavior ?? "smooth"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
context.publishEvent("faq:scroll_to", { itemId: id });
|
|
32
|
+
return {
|
|
33
|
+
cleanup: () => {
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function executeToggleFaqItem(action, context, store) {
|
|
38
|
+
const item = resolveItem(store, action.itemId, action.itemQuestion);
|
|
39
|
+
const { id } = item.config;
|
|
40
|
+
const desiredState = action.state ?? "toggle";
|
|
41
|
+
let newState;
|
|
42
|
+
switch (desiredState) {
|
|
43
|
+
case "open":
|
|
44
|
+
store.expand(id);
|
|
45
|
+
newState = "open";
|
|
46
|
+
break;
|
|
47
|
+
case "closed":
|
|
48
|
+
store.collapse(id);
|
|
49
|
+
newState = "closed";
|
|
50
|
+
break;
|
|
51
|
+
default: {
|
|
52
|
+
const wasExpanded = store.getState().expandedItems.has(id);
|
|
53
|
+
store.toggle(id);
|
|
54
|
+
newState = wasExpanded ? "closed" : "open";
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
context.publishEvent("faq:toggle", { itemId: id, newState });
|
|
59
|
+
return {
|
|
60
|
+
cleanup: () => {
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function executeUpdateFaq(action, context, store) {
|
|
65
|
+
switch (action.operation) {
|
|
66
|
+
case "add": {
|
|
67
|
+
const items = action.items ?? [];
|
|
68
|
+
const position = action.position === "prepend" ? "prepend" : "append";
|
|
69
|
+
store.addItems(items, position);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "remove": {
|
|
73
|
+
if (!action.itemId) {
|
|
74
|
+
throw new Error("FAQ item not found");
|
|
75
|
+
}
|
|
76
|
+
const exists = store.getState().items.some((i) => i.config.id === action.itemId);
|
|
77
|
+
if (!exists) {
|
|
78
|
+
throw new Error("FAQ item not found");
|
|
79
|
+
}
|
|
80
|
+
store.removeItem(action.itemId);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case "reorder": {
|
|
84
|
+
const order = action.order ?? [];
|
|
85
|
+
store.reorderItems(order);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "replace": {
|
|
89
|
+
const items = action.items ?? [];
|
|
90
|
+
store.replaceItems(items);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
context.publishEvent("faq:update", { operation: action.operation });
|
|
95
|
+
return {
|
|
96
|
+
cleanup: () => {
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
var executorDefinitions = [
|
|
101
|
+
{ kind: "faq:scroll_to", executor: executeScrollToFaq },
|
|
102
|
+
{ kind: "faq:toggle_item", executor: executeToggleFaqItem },
|
|
103
|
+
{ kind: "faq:update", executor: executeUpdateFaq }
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// src/FAQWidgetLit.ts
|
|
107
|
+
import { html, LitElement, nothing } from "lit";
|
|
108
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
109
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
110
|
+
import { Marked } from "marked";
|
|
111
|
+
|
|
112
|
+
// src/faq-styles.ts
|
|
113
|
+
var baseStyles = {
|
|
114
|
+
container: {
|
|
115
|
+
fontFamily: "var(--sc-font-family, system-ui, -apple-system, sans-serif)",
|
|
116
|
+
maxWidth: "800px",
|
|
117
|
+
margin: "0 auto"
|
|
118
|
+
},
|
|
119
|
+
searchWrapper: {
|
|
120
|
+
marginBottom: "8px"
|
|
121
|
+
},
|
|
122
|
+
searchInput: {
|
|
123
|
+
width: "100%",
|
|
124
|
+
padding: "12px 16px",
|
|
125
|
+
borderRadius: "8px",
|
|
126
|
+
fontSize: "14px",
|
|
127
|
+
outline: "none",
|
|
128
|
+
transition: "border-color 0.15s ease",
|
|
129
|
+
backgroundColor: "var(--sc-content-search-background)",
|
|
130
|
+
color: "var(--sc-content-search-color)"
|
|
131
|
+
},
|
|
132
|
+
accordion: {
|
|
133
|
+
display: "flex",
|
|
134
|
+
flexDirection: "column",
|
|
135
|
+
gap: "var(--sc-content-item-gap, 6px)"
|
|
136
|
+
},
|
|
137
|
+
item: {
|
|
138
|
+
borderRadius: "var(--sc-content-border-radius, 8px)",
|
|
139
|
+
overflow: "hidden",
|
|
140
|
+
transition: "box-shadow 0.15s ease"
|
|
141
|
+
},
|
|
142
|
+
question: {
|
|
143
|
+
width: "100%",
|
|
144
|
+
padding: "var(--sc-content-item-padding, 12px 16px)",
|
|
145
|
+
display: "flex",
|
|
146
|
+
alignItems: "center",
|
|
147
|
+
justifyContent: "space-between",
|
|
148
|
+
border: "none",
|
|
149
|
+
cursor: "pointer",
|
|
150
|
+
fontSize: "var(--sc-content-item-font-size, 15px)",
|
|
151
|
+
fontWeight: 500,
|
|
152
|
+
textAlign: "left",
|
|
153
|
+
transition: "background-color 0.15s ease"
|
|
154
|
+
},
|
|
155
|
+
chevron: {
|
|
156
|
+
fontSize: "20px",
|
|
157
|
+
transition: "transform 0.2s ease",
|
|
158
|
+
color: "var(--sc-content-chevron-color, currentColor)"
|
|
159
|
+
},
|
|
160
|
+
answer: {
|
|
161
|
+
padding: "var(--sc-content-body-padding, 0 16px 12px 16px)",
|
|
162
|
+
fontSize: "var(--sc-content-body-font-size, 14px)",
|
|
163
|
+
lineHeight: 1.6,
|
|
164
|
+
overflow: "hidden",
|
|
165
|
+
transition: "max-height 0.2s ease, padding 0.2s ease"
|
|
166
|
+
},
|
|
167
|
+
category: {
|
|
168
|
+
display: "inline-block",
|
|
169
|
+
fontSize: "11px",
|
|
170
|
+
fontWeight: 600,
|
|
171
|
+
textTransform: "uppercase",
|
|
172
|
+
letterSpacing: "0.05em",
|
|
173
|
+
padding: "4px 8px",
|
|
174
|
+
borderRadius: "4px",
|
|
175
|
+
marginBottom: "8px"
|
|
176
|
+
},
|
|
177
|
+
categoryHeader: {
|
|
178
|
+
fontSize: "var(--sc-content-category-font-size, 12px)",
|
|
179
|
+
fontWeight: 700,
|
|
180
|
+
textTransform: "uppercase",
|
|
181
|
+
letterSpacing: "0.05em",
|
|
182
|
+
padding: "var(--sc-content-category-padding, 8px 4px 4px 4px)",
|
|
183
|
+
marginTop: "var(--sc-content-category-gap, 4px)"
|
|
184
|
+
},
|
|
185
|
+
feedback: {
|
|
186
|
+
display: "flex",
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
gap: "8px",
|
|
189
|
+
marginTop: "12px",
|
|
190
|
+
paddingTop: "10px",
|
|
191
|
+
borderTop: "1px solid rgba(0, 0, 0, 0.08)",
|
|
192
|
+
fontSize: "13px"
|
|
193
|
+
},
|
|
194
|
+
feedbackButton: {
|
|
195
|
+
background: "none",
|
|
196
|
+
border: "1px solid transparent",
|
|
197
|
+
cursor: "pointer",
|
|
198
|
+
fontSize: "16px",
|
|
199
|
+
padding: "4px 8px",
|
|
200
|
+
borderRadius: "4px",
|
|
201
|
+
transition: "background-color 0.15s ease, border-color 0.15s ease"
|
|
202
|
+
},
|
|
203
|
+
feedbackButtonSelected: {
|
|
204
|
+
borderColor: "rgba(0, 0, 0, 0.2)",
|
|
205
|
+
backgroundColor: "rgba(0, 0, 0, 0.04)"
|
|
206
|
+
},
|
|
207
|
+
emptyState: {
|
|
208
|
+
textAlign: "center",
|
|
209
|
+
padding: "48px 24px",
|
|
210
|
+
fontSize: "14px"
|
|
211
|
+
},
|
|
212
|
+
noResults: {
|
|
213
|
+
textAlign: "center",
|
|
214
|
+
padding: "32px 16px",
|
|
215
|
+
fontSize: "14px"
|
|
216
|
+
}
|
|
36
217
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
218
|
+
var themeStyles = {
|
|
219
|
+
light: {
|
|
220
|
+
container: {
|
|
221
|
+
backgroundColor: "transparent",
|
|
222
|
+
color: "inherit"
|
|
223
|
+
},
|
|
224
|
+
searchInput: {
|
|
225
|
+
border: `1px solid ${slateGrey[11]}`
|
|
226
|
+
},
|
|
227
|
+
item: {
|
|
228
|
+
backgroundColor: "var(--sc-content-background)",
|
|
229
|
+
borderTop: "var(--sc-content-border)",
|
|
230
|
+
borderRight: "var(--sc-content-border)",
|
|
231
|
+
borderBottom: "var(--sc-content-border)",
|
|
232
|
+
borderLeft: "var(--sc-content-border)"
|
|
233
|
+
},
|
|
234
|
+
itemExpanded: {
|
|
235
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)"
|
|
236
|
+
},
|
|
237
|
+
question: {
|
|
238
|
+
backgroundColor: "transparent",
|
|
239
|
+
color: "var(--sc-content-text-color)"
|
|
240
|
+
},
|
|
241
|
+
questionHover: {
|
|
242
|
+
backgroundColor: "var(--sc-content-background-hover)"
|
|
243
|
+
},
|
|
244
|
+
answer: {
|
|
245
|
+
color: "var(--sc-content-text-secondary-color)"
|
|
246
|
+
},
|
|
247
|
+
category: {
|
|
248
|
+
backgroundColor: purple[8],
|
|
249
|
+
color: purple[2]
|
|
250
|
+
},
|
|
251
|
+
categoryHeader: {
|
|
252
|
+
color: slateGrey[7]
|
|
253
|
+
},
|
|
254
|
+
emptyState: {
|
|
255
|
+
color: slateGrey[8]
|
|
256
|
+
},
|
|
257
|
+
feedbackPrompt: {
|
|
258
|
+
color: slateGrey[7]
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
dark: {
|
|
262
|
+
container: {
|
|
263
|
+
backgroundColor: "transparent",
|
|
264
|
+
color: "inherit"
|
|
265
|
+
},
|
|
266
|
+
searchInput: {
|
|
267
|
+
border: `1px solid ${slateGrey[5]}`
|
|
268
|
+
},
|
|
269
|
+
item: {
|
|
270
|
+
backgroundColor: "var(--sc-content-background)",
|
|
271
|
+
borderTop: "var(--sc-content-border)",
|
|
272
|
+
borderRight: "var(--sc-content-border)",
|
|
273
|
+
borderBottom: "var(--sc-content-border)",
|
|
274
|
+
borderLeft: "var(--sc-content-border)"
|
|
92
275
|
},
|
|
276
|
+
itemExpanded: {
|
|
277
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)"
|
|
278
|
+
},
|
|
279
|
+
question: {
|
|
280
|
+
backgroundColor: "transparent",
|
|
281
|
+
color: "var(--sc-content-text-color)"
|
|
282
|
+
},
|
|
283
|
+
questionHover: {
|
|
284
|
+
backgroundColor: "var(--sc-content-background-hover)"
|
|
285
|
+
},
|
|
286
|
+
answer: {
|
|
287
|
+
color: "var(--sc-content-text-secondary-color)"
|
|
288
|
+
},
|
|
289
|
+
category: {
|
|
290
|
+
backgroundColor: purple[0],
|
|
291
|
+
color: purple[6]
|
|
292
|
+
},
|
|
293
|
+
categoryHeader: {
|
|
294
|
+
color: slateGrey[8]
|
|
295
|
+
},
|
|
296
|
+
emptyState: {
|
|
297
|
+
color: slateGrey[7]
|
|
298
|
+
},
|
|
299
|
+
feedbackPrompt: {
|
|
300
|
+
color: slateGrey[8]
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/FAQWidgetLit.ts
|
|
306
|
+
function sm(styles) {
|
|
307
|
+
return styles;
|
|
308
|
+
}
|
|
309
|
+
var marked = new Marked({ async: false, gfm: true, breaks: true });
|
|
310
|
+
function getAnswerText(answer) {
|
|
311
|
+
if (typeof answer === "string") return answer;
|
|
312
|
+
if (answer.type === "rich") return answer.html;
|
|
313
|
+
return answer.content;
|
|
314
|
+
}
|
|
315
|
+
function renderAnswerHtml(answer) {
|
|
316
|
+
if (typeof answer === "string") {
|
|
317
|
+
return marked.parse(answer);
|
|
318
|
+
}
|
|
319
|
+
if (answer.type === "rich") {
|
|
320
|
+
return answer.html;
|
|
321
|
+
}
|
|
322
|
+
return marked.parse(answer.content);
|
|
323
|
+
}
|
|
324
|
+
function resolveFeedbackConfig(feedback) {
|
|
325
|
+
if (!feedback) return null;
|
|
326
|
+
if (feedback === true) return { style: "thumbs" };
|
|
327
|
+
return feedback;
|
|
328
|
+
}
|
|
329
|
+
function getFeedbackPrompt(feedbackConfig) {
|
|
330
|
+
return feedbackConfig.prompt ?? "Was this helpful?";
|
|
331
|
+
}
|
|
332
|
+
function resolveTheme(theme) {
|
|
333
|
+
if (theme && theme !== "auto") return theme;
|
|
334
|
+
if (typeof window !== "undefined") {
|
|
335
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
336
|
+
}
|
|
337
|
+
return "light";
|
|
338
|
+
}
|
|
339
|
+
var FAQAccordionElement = class extends LitElement {
|
|
340
|
+
constructor() {
|
|
341
|
+
super(...arguments);
|
|
342
|
+
// -----------------------------------------------------------------------
|
|
343
|
+
// Property declarations
|
|
344
|
+
// -----------------------------------------------------------------------
|
|
345
|
+
this.faqConfig = {
|
|
346
|
+
expandBehavior: "single",
|
|
347
|
+
searchable: false,
|
|
348
|
+
theme: "auto",
|
|
349
|
+
actions: []
|
|
350
|
+
};
|
|
351
|
+
this.runtime = null;
|
|
352
|
+
this.instanceId = "faq-widget";
|
|
353
|
+
// Internal state
|
|
354
|
+
this._expandedIds = /* @__PURE__ */ new Set();
|
|
355
|
+
this._highlightId = null;
|
|
356
|
+
this._searchQuery = "";
|
|
357
|
+
this._feedbackState = /* @__PURE__ */ new Map();
|
|
358
|
+
this._hoveredId = null;
|
|
359
|
+
// Subscription cleanup handles
|
|
360
|
+
this._unsubContext = null;
|
|
361
|
+
this._unsubAccumulator = null;
|
|
362
|
+
this._unsubCta = null;
|
|
363
|
+
this._unsubDeepLink = null;
|
|
364
|
+
this._unsubSessionMetrics = null;
|
|
365
|
+
this._highlightTimer = null;
|
|
366
|
+
}
|
|
367
|
+
// -----------------------------------------------------------------------
|
|
368
|
+
// Light DOM — no Shadow DOM so CSS variables from the host page apply
|
|
369
|
+
// -----------------------------------------------------------------------
|
|
370
|
+
createRenderRoot() {
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
// -----------------------------------------------------------------------
|
|
374
|
+
// Lifecycle
|
|
375
|
+
// -----------------------------------------------------------------------
|
|
376
|
+
connectedCallback() {
|
|
377
|
+
super.connectedCallback();
|
|
378
|
+
this._subscribeAll();
|
|
379
|
+
}
|
|
380
|
+
disconnectedCallback() {
|
|
381
|
+
super.disconnectedCallback();
|
|
382
|
+
this._unsubscribeAll();
|
|
383
|
+
if (this._highlightTimer !== null) {
|
|
384
|
+
clearTimeout(this._highlightTimer);
|
|
385
|
+
this._highlightTimer = null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Re-subscribe when runtime changes (property may be set after connectedCallback)
|
|
389
|
+
updated(changedProps) {
|
|
390
|
+
if (changedProps.has("runtime")) {
|
|
391
|
+
this._unsubscribeAll();
|
|
392
|
+
this._subscribeAll();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// -----------------------------------------------------------------------
|
|
396
|
+
// Subscription management
|
|
397
|
+
// -----------------------------------------------------------------------
|
|
398
|
+
_subscribeAll() {
|
|
399
|
+
if (!this.runtime) return;
|
|
400
|
+
this._unsubContext = this.runtime.context.subscribe(() => {
|
|
401
|
+
this.requestUpdate();
|
|
402
|
+
});
|
|
403
|
+
if (this.runtime.accumulator?.subscribe) {
|
|
404
|
+
this._unsubAccumulator = this.runtime.accumulator.subscribe(() => {
|
|
405
|
+
this.requestUpdate();
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (this.runtime.sessionMetrics?.subscribe) {
|
|
409
|
+
this._unsubSessionMetrics = this.runtime.sessionMetrics.subscribe(() => {
|
|
410
|
+
this.requestUpdate();
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (this.runtime.events.subscribe) {
|
|
414
|
+
if (this.runtime.events.getRecent) {
|
|
415
|
+
const recentEvents = this.runtime.events.getRecent(
|
|
416
|
+
{ patterns: ["^action\\.tooltip_cta_clicked$", "^action\\.modal_cta_clicked$"] },
|
|
417
|
+
10
|
|
418
|
+
);
|
|
419
|
+
const pendingEvent = recentEvents.filter((e) => {
|
|
420
|
+
const actionId = e.props?.actionId;
|
|
421
|
+
return typeof actionId === "string" && actionId.startsWith("faq:open:");
|
|
422
|
+
}).pop();
|
|
423
|
+
if (pendingEvent && Date.now() - pendingEvent.ts < 1e4) {
|
|
424
|
+
const questionId = pendingEvent.props.actionId.replace("faq:open:", "");
|
|
425
|
+
this._expandedIds = /* @__PURE__ */ new Set([questionId]);
|
|
426
|
+
requestAnimationFrame(() => {
|
|
427
|
+
const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
|
|
428
|
+
if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
this._unsubCta = this.runtime.events.subscribe(
|
|
433
|
+
{ patterns: ["^action\\.tooltip_cta_clicked$", "^action\\.modal_cta_clicked$"] },
|
|
434
|
+
(event) => {
|
|
435
|
+
const actionId = event.props?.actionId;
|
|
436
|
+
if (typeof actionId !== "string" || !actionId.startsWith("faq:open:")) return;
|
|
437
|
+
const questionId = actionId.replace("faq:open:", "");
|
|
438
|
+
this._expandedIds = /* @__PURE__ */ new Set([questionId]);
|
|
439
|
+
requestAnimationFrame(() => {
|
|
440
|
+
const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
|
|
441
|
+
if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
442
|
+
});
|
|
443
|
+
this.runtime?.events.publish("canvas.requestOpen");
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (this.runtime.events.subscribe) {
|
|
448
|
+
const handleDeepLink = (event) => {
|
|
449
|
+
const tileId = event.props?.tileId;
|
|
450
|
+
const itemId = event.props?.itemId;
|
|
451
|
+
if (tileId !== this.instanceId) return;
|
|
452
|
+
if (!itemId) return;
|
|
453
|
+
this._expandedIds = /* @__PURE__ */ new Set([itemId]);
|
|
454
|
+
this._highlightId = itemId;
|
|
455
|
+
if (this._highlightTimer !== null) clearTimeout(this._highlightTimer);
|
|
456
|
+
this._highlightTimer = setTimeout(() => {
|
|
457
|
+
this._highlightId = null;
|
|
458
|
+
this._highlightTimer = null;
|
|
459
|
+
}, 1500);
|
|
460
|
+
requestAnimationFrame(() => {
|
|
461
|
+
const el = document.querySelector(`[data-faq-item-id="${itemId}"]`);
|
|
462
|
+
if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
463
|
+
});
|
|
464
|
+
};
|
|
465
|
+
if (this.runtime.events.getRecent) {
|
|
466
|
+
const recent = this.runtime.events.getRecent({ names: ["notification.deep_link"] }, 5);
|
|
467
|
+
const pending = recent.filter((e) => e.props?.tileId === this.instanceId && e.props?.itemId).pop();
|
|
468
|
+
if (pending && Date.now() - pending.ts < 1e4) {
|
|
469
|
+
handleDeepLink(pending);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
this._unsubDeepLink = this.runtime.events.subscribe(
|
|
473
|
+
{ names: ["notification.deep_link"] },
|
|
474
|
+
handleDeepLink
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
_unsubscribeAll() {
|
|
479
|
+
this._unsubContext?.();
|
|
480
|
+
this._unsubAccumulator?.();
|
|
481
|
+
this._unsubSessionMetrics?.();
|
|
482
|
+
this._unsubCta?.();
|
|
483
|
+
this._unsubDeepLink?.();
|
|
484
|
+
this._unsubContext = null;
|
|
485
|
+
this._unsubAccumulator = null;
|
|
486
|
+
this._unsubSessionMetrics = null;
|
|
487
|
+
this._unsubCta = null;
|
|
488
|
+
this._unsubDeepLink = null;
|
|
489
|
+
}
|
|
490
|
+
// -----------------------------------------------------------------------
|
|
491
|
+
// Handlers
|
|
492
|
+
// -----------------------------------------------------------------------
|
|
493
|
+
_handleToggle(id) {
|
|
494
|
+
const prev = this._expandedIds;
|
|
495
|
+
let next;
|
|
496
|
+
if (this.faqConfig.expandBehavior === "single") {
|
|
497
|
+
next = prev.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]);
|
|
498
|
+
} else {
|
|
499
|
+
next = new Set(prev);
|
|
500
|
+
if (prev.has(id)) {
|
|
501
|
+
next.delete(id);
|
|
502
|
+
} else {
|
|
503
|
+
next.add(id);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const willBeExpanded = !prev.has(id);
|
|
507
|
+
this._expandedIds = next;
|
|
508
|
+
this.runtime?.events.publish("faq:toggled", {
|
|
509
|
+
instanceId: this.instanceId,
|
|
510
|
+
questionId: id,
|
|
511
|
+
expanded: willBeExpanded,
|
|
512
|
+
timestamp: Date.now()
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
_handleFeedback(itemId, question, value) {
|
|
516
|
+
const next = new Map(this._feedbackState);
|
|
517
|
+
next.set(itemId, value);
|
|
518
|
+
this._feedbackState = next;
|
|
519
|
+
this.runtime?.events.publish("faq:feedback", { itemId, question, value });
|
|
520
|
+
}
|
|
521
|
+
// -----------------------------------------------------------------------
|
|
522
|
+
// Computed helpers
|
|
523
|
+
// -----------------------------------------------------------------------
|
|
524
|
+
_visibleQuestions() {
|
|
525
|
+
return (this.faqConfig.actions ?? []).filter((q) => {
|
|
526
|
+
if (!q.triggerWhen) return true;
|
|
527
|
+
if (!this.runtime) return true;
|
|
528
|
+
const result = this.runtime.evaluateSync(q.triggerWhen);
|
|
529
|
+
return result.value;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
_orderedQuestions(visible) {
|
|
533
|
+
if (this.faqConfig.ordering === "priority") {
|
|
534
|
+
return [...visible].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
|
|
535
|
+
}
|
|
536
|
+
return visible;
|
|
537
|
+
}
|
|
538
|
+
_filteredQuestions(ordered) {
|
|
539
|
+
const q = this._searchQuery.trim().toLowerCase();
|
|
540
|
+
if (!this.faqConfig.searchable || !q) return ordered;
|
|
541
|
+
return ordered.filter(
|
|
542
|
+
(item) => item.config.question.toLowerCase().includes(q) || getAnswerText(item.config.answer).toLowerCase().includes(q) || item.config.category?.toLowerCase().includes(q)
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
_categoryGroups(filtered) {
|
|
546
|
+
const groups = /* @__PURE__ */ new Map();
|
|
547
|
+
for (const item of filtered) {
|
|
548
|
+
const cat = item.config.category;
|
|
549
|
+
if (!groups.has(cat)) groups.set(cat, []);
|
|
550
|
+
groups.get(cat).push(item);
|
|
551
|
+
}
|
|
552
|
+
return groups;
|
|
553
|
+
}
|
|
554
|
+
// -----------------------------------------------------------------------
|
|
555
|
+
// Render helpers
|
|
556
|
+
// -----------------------------------------------------------------------
|
|
557
|
+
_renderAnswer(answer) {
|
|
558
|
+
const html_str = renderAnswerHtml(answer);
|
|
559
|
+
return html`<div style="margin:0" data-faq-markdown="">${unsafeHTML(html_str)}</div>`;
|
|
560
|
+
}
|
|
561
|
+
_renderFeedback(item, feedbackConfig, feedbackValue, theme) {
|
|
562
|
+
const colors = themeStyles[theme];
|
|
563
|
+
const feedbackStyle = { ...baseStyles.feedback, ...colors.feedbackPrompt };
|
|
564
|
+
return html`
|
|
565
|
+
<div style=${styleMap(sm(feedbackStyle))}>
|
|
566
|
+
<span>${getFeedbackPrompt(feedbackConfig)}</span>
|
|
567
|
+
<button
|
|
568
|
+
type="button"
|
|
569
|
+
style=${styleMap(
|
|
570
|
+
sm({
|
|
571
|
+
...baseStyles.feedbackButton,
|
|
572
|
+
...feedbackValue === "up" ? baseStyles.feedbackButtonSelected : {}
|
|
573
|
+
})
|
|
574
|
+
)}
|
|
575
|
+
aria-label="Thumbs up"
|
|
576
|
+
@click=${() => this._handleFeedback(item.config.id, item.config.question, "up")}
|
|
577
|
+
>\uD83D\uDC4D</button>
|
|
578
|
+
<button
|
|
579
|
+
type="button"
|
|
580
|
+
style=${styleMap(
|
|
581
|
+
sm({
|
|
582
|
+
...baseStyles.feedbackButton,
|
|
583
|
+
...feedbackValue === "down" ? baseStyles.feedbackButtonSelected : {}
|
|
584
|
+
})
|
|
585
|
+
)}
|
|
586
|
+
aria-label="Thumbs down"
|
|
587
|
+
@click=${() => this._handleFeedback(item.config.id, item.config.question, "down")}
|
|
588
|
+
>\uD83D\uDC4E</button>
|
|
589
|
+
</div>
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
592
|
+
_renderItem(item, isLast, theme, feedbackConfig) {
|
|
593
|
+
const colors = themeStyles[theme];
|
|
594
|
+
const isExpanded = this._expandedIds.has(item.config.id);
|
|
595
|
+
const isHighlighted = this._highlightId === item.config.id;
|
|
596
|
+
const isHovered = this._hoveredId === item.config.id;
|
|
597
|
+
const itemStyle = {
|
|
598
|
+
...baseStyles.item,
|
|
599
|
+
...colors.item,
|
|
600
|
+
...isExpanded ? colors.itemExpanded : {},
|
|
601
|
+
...isHighlighted ? {
|
|
602
|
+
boxShadow: `0 0 0 2px ${purple[4]}, 0 0 12px rgba(106, 89, 206, 0.4)`,
|
|
603
|
+
transition: "box-shadow 0.3s ease"
|
|
604
|
+
} : {},
|
|
605
|
+
...!isLast ? { borderBottom: "var(--sc-content-item-divider, none)" } : {}
|
|
606
|
+
};
|
|
607
|
+
const questionStyle = {
|
|
608
|
+
...baseStyles.question,
|
|
609
|
+
...colors.question,
|
|
610
|
+
...isHovered ? colors.questionHover : {}
|
|
611
|
+
};
|
|
612
|
+
const chevronStyle = {
|
|
613
|
+
...baseStyles.chevron,
|
|
614
|
+
transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)"
|
|
615
|
+
};
|
|
616
|
+
const answerStyle = {
|
|
617
|
+
...baseStyles.answer,
|
|
618
|
+
...colors.answer,
|
|
619
|
+
maxHeight: isExpanded ? "500px" : "0",
|
|
620
|
+
paddingBottom: isExpanded ? "16px" : "0"
|
|
621
|
+
};
|
|
622
|
+
return html`
|
|
623
|
+
<div
|
|
624
|
+
style=${styleMap(sm(itemStyle))}
|
|
625
|
+
data-faq-item-id=${item.config.id}
|
|
626
|
+
>
|
|
627
|
+
<button
|
|
628
|
+
type="button"
|
|
629
|
+
style=${styleMap(sm(questionStyle))}
|
|
630
|
+
aria-expanded=${isExpanded}
|
|
631
|
+
@click=${() => this._handleToggle(item.config.id)}
|
|
632
|
+
@mouseenter=${() => {
|
|
633
|
+
this._hoveredId = item.config.id;
|
|
634
|
+
}}
|
|
635
|
+
@mouseleave=${() => {
|
|
636
|
+
this._hoveredId = null;
|
|
637
|
+
}}
|
|
638
|
+
>
|
|
639
|
+
<span>${item.config.question}</span>
|
|
640
|
+
<span style=${styleMap(sm(chevronStyle))}>\u203A</span>
|
|
641
|
+
</button>
|
|
642
|
+
|
|
643
|
+
<div
|
|
644
|
+
style=${styleMap(sm(answerStyle))}
|
|
645
|
+
aria-hidden=${!isExpanded}
|
|
646
|
+
>
|
|
647
|
+
${this._renderAnswer(item.config.answer)}
|
|
648
|
+
${isExpanded && feedbackConfig ? this._renderFeedback(
|
|
649
|
+
item,
|
|
650
|
+
feedbackConfig,
|
|
651
|
+
this._feedbackState.get(item.config.id),
|
|
652
|
+
theme
|
|
653
|
+
) : nothing}
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
`;
|
|
657
|
+
}
|
|
658
|
+
_renderItems(items, theme, feedbackConfig) {
|
|
659
|
+
return items.map(
|
|
660
|
+
(item, index) => this._renderItem(item, index === items.length - 1, theme, feedbackConfig)
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
// -----------------------------------------------------------------------
|
|
664
|
+
// Render
|
|
665
|
+
// -----------------------------------------------------------------------
|
|
666
|
+
render() {
|
|
667
|
+
const theme = resolveTheme(this.faqConfig.theme);
|
|
668
|
+
const colors = themeStyles[theme];
|
|
669
|
+
const feedbackConfig = resolveFeedbackConfig(this.faqConfig.feedback);
|
|
670
|
+
const visible = this._visibleQuestions();
|
|
671
|
+
const ordered = this._orderedQuestions(visible);
|
|
672
|
+
const filtered = this._filteredQuestions(ordered);
|
|
673
|
+
const hasCategories = filtered.some((q) => q.config.category);
|
|
674
|
+
const groups = hasCategories ? this._categoryGroups(filtered) : null;
|
|
675
|
+
const containerStyle = {
|
|
676
|
+
...baseStyles.container,
|
|
677
|
+
...colors.container
|
|
678
|
+
};
|
|
679
|
+
const emptyStateStyle = {
|
|
680
|
+
...baseStyles.emptyState,
|
|
681
|
+
...colors.emptyState
|
|
682
|
+
};
|
|
683
|
+
const categoryHeaderStyle = {
|
|
684
|
+
...baseStyles.categoryHeader,
|
|
685
|
+
...colors.categoryHeader
|
|
686
|
+
};
|
|
687
|
+
const searchInputStyle = {
|
|
688
|
+
...baseStyles.searchInput,
|
|
689
|
+
...colors.searchInput
|
|
690
|
+
};
|
|
691
|
+
if (visible.length === 0) {
|
|
692
|
+
return html`
|
|
693
|
+
<div
|
|
694
|
+
style=${styleMap(sm(containerStyle))}
|
|
695
|
+
data-adaptive-id=${this.instanceId}
|
|
696
|
+
data-adaptive-type="adaptive-faq"
|
|
697
|
+
>
|
|
698
|
+
<div style=${styleMap(sm(emptyStateStyle))}>
|
|
699
|
+
You're all set for now! We'll surface answers here when they're relevant to what
|
|
700
|
+
you're doing.
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
`;
|
|
704
|
+
}
|
|
705
|
+
return html`
|
|
706
|
+
<div
|
|
707
|
+
style=${styleMap(sm(containerStyle))}
|
|
708
|
+
data-adaptive-id=${this.instanceId}
|
|
709
|
+
data-adaptive-type="adaptive-faq"
|
|
710
|
+
>
|
|
711
|
+
${this.faqConfig.searchable ? html`
|
|
712
|
+
<div style=${styleMap(sm(baseStyles.searchWrapper))}>
|
|
713
|
+
<style>
|
|
714
|
+
[data-adaptive-id="${this.instanceId}"] input::placeholder {
|
|
715
|
+
color: var(--sc-content-search-color, inherit);
|
|
716
|
+
opacity: 0.7;
|
|
717
|
+
}
|
|
718
|
+
</style>
|
|
719
|
+
<input
|
|
720
|
+
type="text"
|
|
721
|
+
placeholder="Search questions..."
|
|
722
|
+
.value=${this._searchQuery}
|
|
723
|
+
style=${styleMap(sm(searchInputStyle))}
|
|
724
|
+
@input=${(e) => {
|
|
725
|
+
this._searchQuery = e.target.value;
|
|
726
|
+
}}
|
|
727
|
+
/>
|
|
728
|
+
</div>
|
|
729
|
+
` : nothing}
|
|
730
|
+
|
|
731
|
+
<div style=${styleMap(sm(baseStyles.accordion))}>
|
|
732
|
+
${groups ? Array.from(groups.entries()).map(
|
|
733
|
+
([category, items]) => html`
|
|
734
|
+
${category ? html`
|
|
735
|
+
<div
|
|
736
|
+
style=${styleMap(sm(categoryHeaderStyle))}
|
|
737
|
+
data-category-header=${category}
|
|
738
|
+
>
|
|
739
|
+
${category}
|
|
740
|
+
</div>
|
|
741
|
+
` : nothing}
|
|
742
|
+
${this._renderItems(items, theme, feedbackConfig)}
|
|
743
|
+
`
|
|
744
|
+
) : this._renderItems(filtered, theme, feedbackConfig)}
|
|
745
|
+
</div>
|
|
746
|
+
|
|
747
|
+
${this.faqConfig.searchable && filtered.length === 0 && this._searchQuery ? html`
|
|
748
|
+
<div
|
|
749
|
+
style=${styleMap(sm({ ...baseStyles.noResults, ...colors.emptyState }))}
|
|
750
|
+
>
|
|
751
|
+
No questions found matching "${this._searchQuery}"
|
|
752
|
+
</div>
|
|
753
|
+
` : nothing}
|
|
754
|
+
</div>
|
|
755
|
+
`;
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
// -----------------------------------------------------------------------
|
|
759
|
+
// Reactive properties (no decorators — tsconfig forbids experimentalDecorators)
|
|
760
|
+
// -----------------------------------------------------------------------
|
|
761
|
+
FAQAccordionElement.properties = {
|
|
762
|
+
// Public API — set from the outside
|
|
763
|
+
faqConfig: { attribute: false },
|
|
764
|
+
runtime: { attribute: false },
|
|
765
|
+
instanceId: { type: String },
|
|
766
|
+
// Internal reactive state (prefixed with _ to signal "private")
|
|
767
|
+
_expandedIds: { state: true },
|
|
768
|
+
_highlightId: { state: true },
|
|
769
|
+
_searchQuery: { state: true },
|
|
770
|
+
_feedbackState: { state: true },
|
|
771
|
+
_hoveredId: { state: true }
|
|
772
|
+
};
|
|
773
|
+
if (!customElements.get("syntro-faq-accordion")) {
|
|
774
|
+
customElements.define("syntro-faq-accordion", FAQAccordionElement);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/runtime.ts
|
|
778
|
+
var FAQWidgetLitMountable = {
|
|
779
|
+
mount(container, config) {
|
|
780
|
+
const {
|
|
781
|
+
runtime: runtime2,
|
|
782
|
+
instanceId = "faq-widget",
|
|
783
|
+
...faqConfig
|
|
784
|
+
} = config ?? {
|
|
785
|
+
expandBehavior: "single",
|
|
786
|
+
searchable: false,
|
|
787
|
+
theme: "auto",
|
|
788
|
+
actions: []
|
|
789
|
+
};
|
|
790
|
+
const el = document.createElement("syntro-faq-accordion");
|
|
791
|
+
Object.assign(el, {
|
|
792
|
+
faqConfig,
|
|
793
|
+
runtime: runtime2 ?? null,
|
|
794
|
+
instanceId
|
|
795
|
+
});
|
|
796
|
+
container.appendChild(el);
|
|
797
|
+
return () => el.remove();
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var runtime = {
|
|
801
|
+
id: "adaptive-faq",
|
|
802
|
+
version: "2.0.0",
|
|
803
|
+
name: "FAQ Accordion",
|
|
804
|
+
description: "Collapsible Q&A accordion with actions, rich content, feedback, and personalization",
|
|
805
|
+
/**
|
|
806
|
+
* Action executors for programmatic FAQ interaction.
|
|
807
|
+
*/
|
|
808
|
+
executors: executorDefinitions,
|
|
809
|
+
/**
|
|
810
|
+
* Widget definitions for the runtime's WidgetRegistry.
|
|
811
|
+
*/
|
|
812
|
+
widgets: [
|
|
813
|
+
{
|
|
814
|
+
id: "adaptive-faq:accordion",
|
|
815
|
+
component: FAQWidgetLitMountable,
|
|
816
|
+
metadata: {
|
|
817
|
+
name: "FAQ Accordion",
|
|
818
|
+
description: "Collapsible Q&A accordion with search, categories, and feedback",
|
|
819
|
+
icon: "\u2753",
|
|
820
|
+
subtitle: "Curated just for you."
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
],
|
|
824
|
+
/**
|
|
825
|
+
* Extract notify watcher entries from tile config props.
|
|
826
|
+
* The runtime evaluates these continuously (even with drawer closed)
|
|
827
|
+
* and publishes faq:question_revealed when triggerWhen transitions false → true.
|
|
828
|
+
*/
|
|
829
|
+
notifyWatchers(props) {
|
|
830
|
+
const actions = props.actions ?? [];
|
|
831
|
+
return actions.filter((a) => a.notify && a.triggerWhen).map((a) => ({
|
|
832
|
+
id: `faq:${a.config.id}`,
|
|
833
|
+
strategy: a.triggerWhen,
|
|
834
|
+
eventName: "faq:question_revealed",
|
|
835
|
+
eventProps: {
|
|
836
|
+
questionId: a.config.id,
|
|
837
|
+
question: a.config.question,
|
|
838
|
+
title: a.notify.title,
|
|
839
|
+
body: a.notify.body,
|
|
840
|
+
icon: a.notify.icon
|
|
841
|
+
}
|
|
842
|
+
}));
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
var runtime_default = runtime;
|
|
846
|
+
export {
|
|
847
|
+
FAQWidgetLitMountable,
|
|
848
|
+
runtime_default as default,
|
|
849
|
+
runtime
|
|
93
850
|
};
|
|
94
|
-
|
|
851
|
+
//# sourceMappingURL=runtime.js.map
|