@syntrologie/adapt-faq 2.8.0-canary.31 → 2.8.0-canary.310

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