@syntrologie/adapt-faq 2.8.0-canary.21 → 2.8.0-canary.211

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 (51) hide show
  1. package/dist/FAQWidgetLit.d.ts +85 -0
  2. package/dist/FAQWidgetLit.d.ts.map +1 -0
  3. package/dist/chunk-5WRI5ZAA.js +31 -0
  4. package/dist/chunk-5WRI5ZAA.js.map +7 -0
  5. package/dist/chunk-S6WIENQP.js +578 -0
  6. package/dist/chunk-S6WIENQP.js.map +7 -0
  7. package/dist/editor.d.ts +35 -33
  8. package/dist/editor.d.ts.map +1 -1
  9. package/dist/editor.js +4821 -308
  10. package/dist/editor.js.map +7 -0
  11. package/dist/faq-styles.d.ts +198 -0
  12. package/dist/faq-styles.d.ts.map +1 -0
  13. package/dist/{FAQWidget.d.ts → faq-types.d.ts} +9 -28
  14. package/dist/faq-types.d.ts.map +1 -0
  15. package/dist/runtime.d.ts +16 -3
  16. package/dist/runtime.d.ts.map +1 -1
  17. package/dist/runtime.js +849 -64
  18. package/dist/runtime.js.map +7 -0
  19. package/dist/schema.d.ts +1077 -545
  20. package/dist/schema.d.ts.map +1 -1
  21. package/dist/schema.js +448 -206
  22. package/dist/schema.js.map +7 -0
  23. package/dist/types.d.ts +19 -0
  24. package/dist/types.d.ts.map +1 -1
  25. package/node_modules/marked/LICENSE.md +44 -0
  26. package/node_modules/marked/README.md +107 -0
  27. package/node_modules/marked/bin/main.js +283 -0
  28. package/node_modules/marked/bin/marked.js +16 -0
  29. package/node_modules/marked/lib/marked.d.ts +759 -0
  30. package/node_modules/marked/lib/marked.esm.js +72 -0
  31. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  32. package/node_modules/marked/lib/marked.umd.js +74 -0
  33. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  34. package/node_modules/marked/man/marked.1 +113 -0
  35. package/node_modules/marked/man/marked.1.md +93 -0
  36. package/node_modules/marked/package.json +103 -0
  37. package/package.json +13 -18
  38. package/dist/FAQWidget.d.ts.map +0 -1
  39. package/dist/FAQWidget.js +0 -581
  40. package/dist/cdn.d.ts +0 -70
  41. package/dist/cdn.d.ts.map +0 -1
  42. package/dist/cdn.js +0 -46
  43. package/dist/executors.js +0 -150
  44. package/dist/state.js +0 -132
  45. package/dist/summarize.js +0 -62
  46. package/dist/types.js +0 -17
  47. package/node_modules/@syntrologie/sdk-contracts/dist/index.d.ts +0 -129
  48. package/node_modules/@syntrologie/sdk-contracts/dist/index.js +0 -15
  49. package/node_modules/@syntrologie/sdk-contracts/dist/schemas.d.ts +0 -2225
  50. package/node_modules/@syntrologie/sdk-contracts/dist/schemas.js +0 -162
  51. package/node_modules/@syntrologie/sdk-contracts/package.json +0 -33
package/dist/FAQWidget.js DELETED
@@ -1,581 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Adaptive FAQ - FAQWidget Component
4
- *
5
- * React component that renders a collapsible Q&A accordion with per-item
6
- * conditional visibility based on triggerWhen decision strategies.
7
- *
8
- * Demonstrates the compositional action pattern where child actions
9
- * (faq:question) serve as configuration data for the parent widget.
10
- */
11
- import { purple, slateGrey } from '@syntro/design-system/tokens';
12
- import { Marked } from 'marked';
13
- import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
14
- import { createRoot } from 'react-dom/client';
15
- const marked = new Marked({ async: false, gfm: true, breaks: true });
16
- // ============================================================================
17
- // Helpers
18
- // ============================================================================
19
- /** Extract plain text from an FAQAnswer for search matching */
20
- function getAnswerText(answer) {
21
- if (typeof answer === 'string')
22
- return answer;
23
- if (answer.type === 'rich')
24
- return answer.html;
25
- return answer.content;
26
- }
27
- /** Render an FAQAnswer based on its type */
28
- function renderAnswer(answer) {
29
- if (typeof answer === 'string') {
30
- return _jsx("p", { style: { margin: 0 }, children: answer });
31
- }
32
- if (answer.type === 'rich') {
33
- // biome-ignore lint/security/noDangerouslySetInnerHtml: content is pre-sanitized by backend — FAQAnswer.html is CMS/config content, not user-controlled input
34
- return _jsx("div", { style: { margin: 0 }, dangerouslySetInnerHTML: { __html: answer.html } });
35
- }
36
- // markdown — parse to HTML and render
37
- const html = marked.parse(answer.content);
38
- return (
39
- // biome-ignore lint/security/noDangerouslySetInnerHtml: markdown content is CMS/config content, not user-controlled input
40
- _jsx("div", { style: { margin: 0 }, "data-faq-markdown": "", dangerouslySetInnerHTML: { __html: html } }));
41
- }
42
- /** Resolve feedback config into a normalized FeedbackConfig or null */
43
- function resolveFeedbackConfig(feedback) {
44
- if (!feedback)
45
- return null;
46
- if (feedback === true) {
47
- return { style: 'thumbs' };
48
- }
49
- return feedback;
50
- }
51
- /** Get the feedback prompt text */
52
- function getFeedbackPrompt(feedbackConfig) {
53
- return feedbackConfig.prompt || 'Was this helpful?';
54
- }
55
- // ============================================================================
56
- // Styles
57
- // ============================================================================
58
- const baseStyles = {
59
- container: {
60
- fontFamily: 'var(--sc-font-family, system-ui, -apple-system, sans-serif)',
61
- maxWidth: '800px',
62
- margin: '0 auto',
63
- },
64
- searchWrapper: {
65
- marginBottom: '8px',
66
- },
67
- searchInput: {
68
- width: '100%',
69
- padding: '12px 16px',
70
- borderRadius: '8px',
71
- fontSize: '14px',
72
- outline: 'none',
73
- transition: 'border-color 0.15s ease',
74
- backgroundColor: 'var(--sc-content-search-background)',
75
- color: 'var(--sc-content-search-color)',
76
- },
77
- accordion: {
78
- display: 'flex',
79
- flexDirection: 'column',
80
- gap: 'var(--sc-content-item-gap, 6px)',
81
- },
82
- item: {
83
- borderRadius: 'var(--sc-content-border-radius, 8px)',
84
- overflow: 'hidden',
85
- transition: 'box-shadow 0.15s ease',
86
- },
87
- question: {
88
- width: '100%',
89
- padding: 'var(--sc-content-item-padding, 12px 16px)',
90
- display: 'flex',
91
- alignItems: 'center',
92
- justifyContent: 'space-between',
93
- border: 'none',
94
- cursor: 'pointer',
95
- fontSize: 'var(--sc-content-item-font-size, 15px)',
96
- fontWeight: 500,
97
- textAlign: 'left',
98
- transition: 'background-color 0.15s ease',
99
- },
100
- chevron: {
101
- fontSize: '20px',
102
- transition: 'transform 0.2s ease',
103
- color: 'var(--sc-content-chevron-color, currentColor)',
104
- },
105
- answer: {
106
- padding: 'var(--sc-content-body-padding, 0 16px 12px 16px)',
107
- fontSize: 'var(--sc-content-body-font-size, 14px)',
108
- lineHeight: 1.6,
109
- overflow: 'hidden',
110
- transition: 'max-height 0.2s ease, padding 0.2s ease',
111
- },
112
- category: {
113
- display: 'inline-block',
114
- fontSize: '11px',
115
- fontWeight: 600,
116
- textTransform: 'uppercase',
117
- letterSpacing: '0.05em',
118
- padding: '4px 8px',
119
- borderRadius: '4px',
120
- marginBottom: '8px',
121
- },
122
- categoryHeader: {
123
- fontSize: 'var(--sc-content-category-font-size, 12px)',
124
- fontWeight: 700,
125
- textTransform: 'uppercase',
126
- letterSpacing: '0.05em',
127
- padding: 'var(--sc-content-category-padding, 8px 4px 4px 4px)',
128
- marginTop: 'var(--sc-content-category-gap, 4px)',
129
- },
130
- feedback: {
131
- display: 'flex',
132
- alignItems: 'center',
133
- gap: '8px',
134
- marginTop: '12px',
135
- paddingTop: '10px',
136
- borderTop: '1px solid rgba(0, 0, 0, 0.08)',
137
- fontSize: '13px',
138
- },
139
- feedbackButton: {
140
- background: 'none',
141
- border: '1px solid transparent',
142
- cursor: 'pointer',
143
- fontSize: '16px',
144
- padding: '4px 8px',
145
- borderRadius: '4px',
146
- transition: 'background-color 0.15s ease, border-color 0.15s ease',
147
- },
148
- feedbackButtonSelected: {
149
- borderColor: 'rgba(0, 0, 0, 0.2)',
150
- backgroundColor: 'rgba(0, 0, 0, 0.04)',
151
- },
152
- emptyState: {
153
- textAlign: 'center',
154
- padding: '48px 24px',
155
- fontSize: '14px',
156
- },
157
- noResults: {
158
- textAlign: 'center',
159
- padding: '32px 16px',
160
- fontSize: '14px',
161
- },
162
- };
163
- const themeStyles = {
164
- light: {
165
- container: {
166
- backgroundColor: 'transparent',
167
- color: 'inherit',
168
- },
169
- searchInput: {
170
- border: `1px solid ${slateGrey[11]}`,
171
- },
172
- item: {
173
- backgroundColor: 'var(--sc-content-background)',
174
- borderTop: 'var(--sc-content-border)',
175
- borderRight: 'var(--sc-content-border)',
176
- borderBottom: 'var(--sc-content-border)',
177
- borderLeft: 'var(--sc-content-border)',
178
- },
179
- itemExpanded: {
180
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
181
- },
182
- question: {
183
- backgroundColor: 'transparent',
184
- color: 'var(--sc-content-text-color)',
185
- },
186
- questionHover: {
187
- backgroundColor: 'var(--sc-content-background-hover)',
188
- },
189
- answer: {
190
- color: 'var(--sc-content-text-secondary-color)',
191
- },
192
- category: {
193
- backgroundColor: purple[8],
194
- color: purple[2],
195
- },
196
- categoryHeader: {
197
- color: slateGrey[7],
198
- },
199
- emptyState: {
200
- color: slateGrey[8],
201
- },
202
- feedbackPrompt: {
203
- color: slateGrey[7],
204
- },
205
- },
206
- dark: {
207
- container: {
208
- backgroundColor: 'transparent',
209
- color: 'inherit',
210
- },
211
- searchInput: {
212
- border: `1px solid ${slateGrey[5]}`,
213
- },
214
- item: {
215
- backgroundColor: 'var(--sc-content-background)',
216
- borderTop: 'var(--sc-content-border)',
217
- borderRight: 'var(--sc-content-border)',
218
- borderBottom: 'var(--sc-content-border)',
219
- borderLeft: 'var(--sc-content-border)',
220
- },
221
- itemExpanded: {
222
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
223
- },
224
- question: {
225
- backgroundColor: 'transparent',
226
- color: 'var(--sc-content-text-color)',
227
- },
228
- questionHover: {
229
- backgroundColor: 'var(--sc-content-background-hover)',
230
- },
231
- answer: {
232
- color: 'var(--sc-content-text-secondary-color)',
233
- },
234
- category: {
235
- backgroundColor: purple[0],
236
- color: purple[6],
237
- },
238
- categoryHeader: {
239
- color: slateGrey[8],
240
- },
241
- emptyState: {
242
- color: slateGrey[7],
243
- },
244
- feedbackPrompt: {
245
- color: slateGrey[8],
246
- },
247
- },
248
- };
249
- function FAQItem({ item, isExpanded, isHighlighted, isLast, onToggle, theme, feedbackConfig, feedbackValue, onFeedback, }) {
250
- const [isHovered, setIsHovered] = useState(false);
251
- const colors = themeStyles[theme];
252
- const { question, answer } = item.config;
253
- const itemStyle = {
254
- ...baseStyles.item,
255
- ...colors.item,
256
- ...(isExpanded ? colors.itemExpanded : {}),
257
- ...(isHighlighted
258
- ? {
259
- boxShadow: '0 0 0 2px #6366f1, 0 0 12px rgba(99, 102, 241, 0.4)',
260
- transition: 'box-shadow 0.3s ease',
261
- }
262
- : {}),
263
- ...(!isLast ? { borderBottom: 'var(--sc-content-item-divider, none)' } : {}),
264
- };
265
- const questionStyle = {
266
- ...baseStyles.question,
267
- ...colors.question,
268
- ...(isHovered ? colors.questionHover : {}),
269
- };
270
- const chevronStyle = {
271
- ...baseStyles.chevron,
272
- transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
273
- };
274
- const answerStyle = {
275
- ...baseStyles.answer,
276
- ...colors.answer,
277
- maxHeight: isExpanded ? '500px' : '0',
278
- paddingBottom: isExpanded ? '16px' : '0',
279
- };
280
- const feedbackStyle = {
281
- ...baseStyles.feedback,
282
- ...colors.feedbackPrompt,
283
- };
284
- return (_jsxs("div", { style: itemStyle, "data-faq-item-id": item.config.id, children: [_jsxs("button", { type: "button", style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx("span", { children: question }), _jsx("span", { style: chevronStyle, children: '\u203A' })] }), _jsxs("div", { style: answerStyle, "aria-hidden": !isExpanded, children: [renderAnswer(answer), isExpanded && feedbackConfig && (_jsxs("div", { style: feedbackStyle, children: [_jsx("span", { children: getFeedbackPrompt(feedbackConfig) }), _jsx("button", { type: "button", style: {
285
- ...baseStyles.feedbackButton,
286
- ...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),
287
- }, "aria-label": "Thumbs up", onClick: () => onFeedback(item.config.id, question, 'up'), children: '\uD83D\uDC4D' }), _jsx("button", { type: "button", style: {
288
- ...baseStyles.feedbackButton,
289
- ...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),
290
- }, "aria-label": "Thumbs down", onClick: () => onFeedback(item.config.id, question, 'down'), children: '\uD83D\uDC4E' })] }))] })] }));
291
- }
292
- // ============================================================================
293
- // FAQWidget Component
294
- // ============================================================================
295
- /**
296
- * FAQWidget - Renders a collapsible Q&A accordion with per-item activation.
297
- *
298
- * This component demonstrates the compositional action pattern:
299
- * - Parent (FAQWidget) receives `config.actions` array
300
- * - Each action has optional `triggerWhen` for per-item visibility
301
- * - Parent evaluates triggerWhen and filters visible questions
302
- * - Parent manages expand state and re-rendering on context changes
303
- */
304
- export function FAQWidget({ config, runtime, instanceId }) {
305
- // Force re-render when context/accumulator changes.
306
- // renderTick is used as a useMemo dependency to invalidate cached triggerWhen evaluations.
307
- const [renderTick, forceUpdate] = useReducer((x) => x + 1, 0);
308
- // Track expanded question IDs
309
- const [expandedIds, setExpandedIds] = useState(new Set());
310
- // Track which item is flash-highlighted from a deep-link
311
- const [highlightId, setHighlightId] = useState(null);
312
- // Search query state
313
- const [searchQuery, setSearchQuery] = useState('');
314
- // Track feedback state per item
315
- const [feedbackState, setFeedbackState] = useState(new Map());
316
- // Resolve feedback config
317
- const feedbackConfig = useMemo(() => resolveFeedbackConfig(config.feedback), [config.feedback]);
318
- // Subscribe to context changes for reactive updates
319
- useEffect(() => {
320
- const unsubscribe = runtime.context.subscribe(() => {
321
- forceUpdate();
322
- });
323
- return unsubscribe;
324
- }, [runtime.context]);
325
- // Subscribe to accumulator changes for event_count-based triggerWhen
326
- useEffect(() => {
327
- if (!runtime.accumulator?.subscribe)
328
- return;
329
- return runtime.accumulator.subscribe(() => {
330
- forceUpdate();
331
- });
332
- }, [runtime.accumulator]);
333
- // Subscribe to faq:open:* events from overlay CTA clicks
334
- useEffect(() => {
335
- if (!runtime.events.subscribe)
336
- return;
337
- // Check EventBus history for pending faq:open events
338
- // (may have fired before this widget mounted, e.g. when canvas was closed)
339
- if (runtime.events.getRecent) {
340
- const recentEvents = runtime.events.getRecent({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, 10);
341
- const pendingEvent = recentEvents
342
- .filter((e) => {
343
- const actionId = e.props?.actionId;
344
- return typeof actionId === 'string' && actionId.startsWith('faq:open:');
345
- })
346
- .pop(); // Most recent
347
- if (pendingEvent && Date.now() - pendingEvent.ts < 10000) {
348
- const questionId = pendingEvent.props.actionId.replace('faq:open:', '');
349
- setExpandedIds(new Set([questionId]));
350
- // Scroll into view after render
351
- requestAnimationFrame(() => {
352
- const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
353
- if (el)
354
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
355
- });
356
- }
357
- }
358
- // Subscribe to future CTA events
359
- const unsubscribe = runtime.events.subscribe({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, (event) => {
360
- const actionId = event.props?.actionId;
361
- if (typeof actionId !== 'string' || !actionId.startsWith('faq:open:'))
362
- return;
363
- const questionId = actionId.replace('faq:open:', '');
364
- setExpandedIds(new Set([questionId]));
365
- // Scroll the question into view
366
- requestAnimationFrame(() => {
367
- const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
368
- if (el)
369
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
370
- });
371
- // Request canvas open (for future tile-based FAQ rendering)
372
- runtime.events.publish('canvas.requestOpen');
373
- });
374
- return unsubscribe;
375
- }, [runtime]);
376
- // Subscribe to notification.deep_link events (from insertHtml deepLink clicks + notification toasts)
377
- useEffect(() => {
378
- if (!runtime.events.subscribe)
379
- return;
380
- const handleDeepLink = (event) => {
381
- const tileId = event.props?.tileId;
382
- const itemId = event.props?.itemId;
383
- // Only handle if this deep link targets our tile
384
- if (tileId !== instanceId)
385
- return;
386
- if (!itemId)
387
- return;
388
- // Expand the target item
389
- setExpandedIds(new Set([itemId]));
390
- // Flash-highlight the item
391
- setHighlightId(itemId);
392
- setTimeout(() => setHighlightId(null), 1500);
393
- // Scroll into view after render
394
- requestAnimationFrame(() => {
395
- const el = document.querySelector(`[data-faq-item-id="${itemId}"]`);
396
- if (el)
397
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
398
- });
399
- };
400
- // Check recent events (may have fired before widget mounted, e.g. canvas was closed)
401
- if (runtime.events.getRecent) {
402
- const recent = runtime.events.getRecent({ names: ['notification.deep_link'] }, 5);
403
- const pending = recent
404
- .filter((e) => e.props?.tileId === instanceId && e.props?.itemId)
405
- .pop();
406
- if (pending && Date.now() - pending.ts < 10000) {
407
- handleDeepLink(pending);
408
- }
409
- }
410
- // Subscribe to future events
411
- const unsubscribe = runtime.events.subscribe({ names: ['notification.deep_link'] }, handleDeepLink);
412
- return unsubscribe;
413
- }, [runtime, instanceId]);
414
- // Filter visible questions based on per-item triggerWhen
415
- // biome-ignore lint/correctness/useExhaustiveDependencies: renderTick is intentionally included to force re-evaluation when the runtime's mutable context changes (subscribed above via forceUpdate)
416
- const visibleQuestions = useMemo(() => config.actions.filter((q) => {
417
- // No triggerWhen = always visible
418
- if (!q.triggerWhen)
419
- return true;
420
- // Evaluate the decision strategy
421
- const result = runtime.evaluateSync(q.triggerWhen);
422
- return result.value;
423
- }), [config.actions, runtime, renderTick]);
424
- // NOTE: faq:question_revealed is now published by useNotifyWatcher in
425
- // ShadowCanvasOverlay (always mounted), so it fires even with drawer closed.
426
- // Apply priority ordering
427
- const orderedQuestions = useMemo(() => {
428
- if (config.ordering === 'priority') {
429
- return [...visibleQuestions].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
430
- }
431
- // 'static' or undefined — preserve config order
432
- return visibleQuestions;
433
- }, [visibleQuestions, config.ordering]);
434
- // Apply search filter
435
- const filteredQuestions = useMemo(() => {
436
- if (!config.searchable || !searchQuery.trim()) {
437
- return orderedQuestions;
438
- }
439
- const query = searchQuery.toLowerCase();
440
- return orderedQuestions.filter((q) => q.config.question.toLowerCase().includes(query) ||
441
- getAnswerText(q.config.answer).toLowerCase().includes(query) ||
442
- q.config.category?.toLowerCase().includes(query));
443
- }, [orderedQuestions, searchQuery, config.searchable]);
444
- // Group by category
445
- const categoryGroups = useMemo(() => {
446
- const groups = new Map();
447
- for (const q of filteredQuestions) {
448
- const cat = q.config.category;
449
- if (!groups.has(cat)) {
450
- groups.set(cat, []);
451
- }
452
- groups.get(cat).push(q);
453
- }
454
- return groups;
455
- }, [filteredQuestions]);
456
- // Check if any items have categories
457
- const hasCategories = useMemo(() => filteredQuestions.some((q) => q.config.category), [filteredQuestions]);
458
- // Resolve theme (auto -> detect system preference)
459
- const resolvedTheme = useMemo(() => {
460
- if (config.theme && config.theme !== 'auto')
461
- return config.theme;
462
- // Check system preference (SSR-safe)
463
- if (typeof window !== 'undefined') {
464
- return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
465
- }
466
- return 'light';
467
- }, [config.theme]);
468
- // Handle question toggle
469
- const handleToggle = useCallback((id) => {
470
- setExpandedIds((prev) => {
471
- const next = new Set(prev);
472
- if (config.expandBehavior === 'single') {
473
- // Single mode: collapse all others
474
- if (prev.has(id)) {
475
- return new Set();
476
- }
477
- return new Set([id]);
478
- }
479
- // Multiple mode: toggle this one
480
- if (prev.has(id)) {
481
- next.delete(id);
482
- }
483
- else {
484
- next.add(id);
485
- }
486
- return next;
487
- });
488
- // Publish toggle event for analytics
489
- runtime.events.publish('faq:toggled', {
490
- instanceId,
491
- questionId: id,
492
- expanded: !expandedIds.has(id),
493
- timestamp: Date.now(),
494
- });
495
- }, [config.expandBehavior, runtime.events, instanceId, expandedIds]);
496
- // Handle feedback
497
- const handleFeedback = useCallback((itemId, question, value) => {
498
- setFeedbackState((prev) => {
499
- const next = new Map(prev);
500
- next.set(itemId, value);
501
- return next;
502
- });
503
- runtime.events.publish('faq:feedback', {
504
- itemId,
505
- question,
506
- value,
507
- });
508
- }, [runtime.events]);
509
- // Compute styles
510
- const containerStyle = {
511
- ...baseStyles.container,
512
- ...themeStyles[resolvedTheme].container,
513
- };
514
- const searchInputStyle = {
515
- ...baseStyles.searchInput,
516
- ...themeStyles[resolvedTheme].searchInput,
517
- };
518
- const emptyStateStyle = {
519
- ...baseStyles.emptyState,
520
- ...themeStyles[resolvedTheme].emptyState,
521
- };
522
- const categoryHeaderStyle = {
523
- ...baseStyles.categoryHeader,
524
- ...themeStyles[resolvedTheme].categoryHeader,
525
- };
526
- // Render a list of FAQ items
527
- const renderItems = (items) => items.map((q, index) => (_jsx(FAQItem, { item: q, isExpanded: expandedIds.has(q.config.id), isHighlighted: highlightId === q.config.id, isLast: index === items.length - 1, onToggle: () => handleToggle(q.config.id), theme: resolvedTheme, feedbackConfig: feedbackConfig, feedbackValue: feedbackState.get(q.config.id), onFeedback: handleFeedback }, q.config.id)));
528
- // Empty state (no visible questions at all)
529
- if (visibleQuestions.length === 0) {
530
- return (_jsx("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: _jsx("div", { style: emptyStateStyle, children: "You're all set for now! We'll surface answers here when they're relevant to what you're doing." }) }));
531
- }
532
- return (_jsxs("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && (_jsxs("div", { style: baseStyles.searchWrapper, children: [_jsx("style", { children: `[data-adaptive-id="${instanceId}"] input::placeholder { color: var(--sc-content-search-color, inherit); opacity: 0.7; }` }), _jsx("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle })] })), _jsx("div", { style: baseStyles.accordion, children: hasCategories
533
- ? Array.from(categoryGroups.entries()).map(([category, items]) => (_jsxs(React.Fragment, { children: [category && (_jsx("div", { style: categoryHeaderStyle, "data-category-header": category, children: category })), renderItems(items)] }, category ?? '__ungrouped')))
534
- : renderItems(filteredQuestions) }), config.searchable && filteredQuestions.length === 0 && searchQuery && (_jsxs("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ["No questions found matching \"", searchQuery, "\""] }))] }));
535
- }
536
- // ============================================================================
537
- // Mountable Widget Interface
538
- // ============================================================================
539
- /**
540
- * Mountable widget interface for the runtime's WidgetRegistry.
541
- */
542
- export const FAQMountableWidget = {
543
- mount(container, config) {
544
- const { runtime, instanceId = 'faq-widget', ...faqConfig } = config || {
545
- expandBehavior: 'single',
546
- searchable: false,
547
- theme: 'auto',
548
- actions: [],
549
- };
550
- // React rendering when runtime + ReactDOM are available
551
- if (runtime && typeof createRoot === 'function') {
552
- const root = createRoot(container);
553
- root.render(React.createElement(FAQWidget, {
554
- config: faqConfig,
555
- runtime: runtime,
556
- instanceId,
557
- }));
558
- return () => {
559
- root.unmount();
560
- };
561
- }
562
- // HTML fallback when React is not available
563
- const questions = faqConfig.actions || [];
564
- container.innerHTML = `
565
- <div style="font-family: system-ui; max-width: 800px;">
566
- ${questions
567
- .map((q) => `
568
- <div style="margin-bottom: 8px; padding: 16px; background: ${slateGrey[12]}; border-radius: 8px;">
569
- <strong>${q.config.question}</strong>
570
- <p style="margin-top: 8px; color: ${slateGrey[6]};">${getAnswerText(q.config.answer)}</p>
571
- </div>
572
- `)
573
- .join('')}
574
- </div>
575
- `;
576
- return () => {
577
- container.innerHTML = '';
578
- };
579
- },
580
- };
581
- export default FAQWidget;
package/dist/cdn.d.ts DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * CDN Entry Point for Adaptive FAQ
3
- *
4
- * This module is bundled for CDN delivery and self-registers with the global
5
- * SynOS app registry when loaded dynamically via the AppLoader.
6
- */
7
- import FAQEditor from './editor';
8
- /**
9
- * App manifest for registry registration.
10
- * Follows the AppManifest interface expected by AppLoader/AppRegistry.
11
- */
12
- export declare const manifest: {
13
- id: string;
14
- version: string;
15
- name: string;
16
- description: string;
17
- runtime: {
18
- actions: readonly [{
19
- readonly kind: "faq:scroll_to";
20
- readonly executor: typeof import("./executors").executeScrollToFaq;
21
- }, {
22
- readonly kind: "faq:toggle_item";
23
- readonly executor: typeof import("./executors").executeToggleFaqItem;
24
- }, {
25
- readonly kind: "faq:update";
26
- readonly executor: typeof import("./executors").executeUpdateFaq;
27
- }];
28
- widgets: {
29
- id: string;
30
- component: {
31
- mount(container: HTMLElement, config?: import("./types").FAQConfig & {
32
- runtime?: import("./FAQWidget").FAQWidgetRuntime;
33
- instanceId?: string;
34
- }): () => void;
35
- };
36
- metadata: {
37
- name: string;
38
- description: string;
39
- icon: string;
40
- subtitle: string;
41
- };
42
- }[];
43
- notifyWatchers: (props: Record<string, unknown>) => {
44
- id: string;
45
- strategy: import("./types").DecisionStrategy<boolean>;
46
- eventName: string;
47
- eventProps: {
48
- questionId: string;
49
- question: string;
50
- title: string | undefined;
51
- body: string | undefined;
52
- icon: string | undefined;
53
- };
54
- }[];
55
- };
56
- editor: {
57
- component: typeof FAQEditor;
58
- panel: {
59
- title: string;
60
- icon: string;
61
- description: string;
62
- };
63
- getActionLabel(action: Record<string, unknown>): string;
64
- };
65
- metadata: {
66
- isBuiltIn: boolean;
67
- };
68
- };
69
- export default manifest;
70
- //# sourceMappingURL=cdn.d.ts.map
package/dist/cdn.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAA0B,MAAM,UAAU,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;2BAoCiouB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAtB3puB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;CAQjD,CAAC;AAaF,eAAe,QAAQ,CAAC"}