@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/FAQWidget.js DELETED
@@ -1,388 +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 { 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
- import { baseStyles, themeStyles } from './faq-styles';
17
- // ============================================================================
18
- // Helpers
19
- // ============================================================================
20
- /** Extract plain text from an FAQAnswer for search matching */
21
- function getAnswerText(answer) {
22
- if (typeof answer === 'string')
23
- return answer;
24
- if (answer.type === 'rich')
25
- return answer.html;
26
- return answer.content;
27
- }
28
- /** Render an FAQAnswer based on its type */
29
- function renderAnswer(answer) {
30
- if (typeof answer === 'string') {
31
- return _jsx("p", { style: { margin: 0 }, children: answer });
32
- }
33
- if (answer.type === 'rich') {
34
- // biome-ignore lint/security/noDangerouslySetInnerHtml: content is pre-sanitized by backend — FAQAnswer.html is CMS/config content, not user-controlled input
35
- return _jsx("div", { style: { margin: 0 }, dangerouslySetInnerHTML: { __html: answer.html } });
36
- }
37
- // markdown — parse to HTML and render
38
- const html = marked.parse(answer.content);
39
- return (
40
- // biome-ignore lint/security/noDangerouslySetInnerHtml: markdown content is CMS/config content, not user-controlled input
41
- _jsx("div", { style: { margin: 0 }, "data-faq-markdown": "", dangerouslySetInnerHTML: { __html: html } }));
42
- }
43
- /** Resolve feedback config into a normalized FeedbackConfig or null */
44
- function resolveFeedbackConfig(feedback) {
45
- if (!feedback)
46
- return null;
47
- if (feedback === true) {
48
- return { style: 'thumbs' };
49
- }
50
- return feedback;
51
- }
52
- /** Get the feedback prompt text */
53
- function getFeedbackPrompt(feedbackConfig) {
54
- return feedbackConfig.prompt || 'Was this helpful?';
55
- }
56
- function FAQItem({ item, isExpanded, isHighlighted, isLast, onToggle, theme, feedbackConfig, feedbackValue, onFeedback, }) {
57
- const [isHovered, setIsHovered] = useState(false);
58
- const colors = themeStyles[theme];
59
- const { question, answer } = item.config;
60
- const itemStyle = {
61
- ...baseStyles.item,
62
- ...colors.item,
63
- ...(isExpanded ? colors.itemExpanded : {}),
64
- ...(isHighlighted
65
- ? {
66
- boxShadow: '0 0 0 2px #6366f1, 0 0 12px rgba(99, 102, 241, 0.4)',
67
- transition: 'box-shadow 0.3s ease',
68
- }
69
- : {}),
70
- ...(!isLast ? { borderBottom: 'var(--sc-content-item-divider, none)' } : {}),
71
- };
72
- const questionStyle = {
73
- ...baseStyles.question,
74
- ...colors.question,
75
- ...(isHovered ? colors.questionHover : {}),
76
- };
77
- const chevronStyle = {
78
- ...baseStyles.chevron,
79
- transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
80
- };
81
- const answerStyle = {
82
- ...baseStyles.answer,
83
- ...colors.answer,
84
- maxHeight: isExpanded ? '500px' : '0',
85
- paddingBottom: isExpanded ? '16px' : '0',
86
- };
87
- const feedbackStyle = {
88
- ...baseStyles.feedback,
89
- ...colors.feedbackPrompt,
90
- };
91
- 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: {
92
- ...baseStyles.feedbackButton,
93
- ...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),
94
- }, "aria-label": "Thumbs up", onClick: () => onFeedback(item.config.id, question, 'up'), children: '\uD83D\uDC4D' }), _jsx("button", { type: "button", style: {
95
- ...baseStyles.feedbackButton,
96
- ...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),
97
- }, "aria-label": "Thumbs down", onClick: () => onFeedback(item.config.id, question, 'down'), children: '\uD83D\uDC4E' })] }))] })] }));
98
- }
99
- // ============================================================================
100
- // FAQWidget Component
101
- // ============================================================================
102
- /**
103
- * FAQWidget - Renders a collapsible Q&A accordion with per-item activation.
104
- *
105
- * This component demonstrates the compositional action pattern:
106
- * - Parent (FAQWidget) receives `config.actions` array
107
- * - Each action has optional `triggerWhen` for per-item visibility
108
- * - Parent evaluates triggerWhen and filters visible questions
109
- * - Parent manages expand state and re-rendering on context changes
110
- */
111
- export function FAQWidget({ config, runtime, instanceId }) {
112
- // Force re-render when context/accumulator changes.
113
- // renderTick is used as a useMemo dependency to invalidate cached triggerWhen evaluations.
114
- const [renderTick, forceUpdate] = useReducer((x) => x + 1, 0);
115
- // Track expanded question IDs
116
- const [expandedIds, setExpandedIds] = useState(new Set());
117
- // Track which item is flash-highlighted from a deep-link
118
- const [highlightId, setHighlightId] = useState(null);
119
- // Search query state
120
- const [searchQuery, setSearchQuery] = useState('');
121
- // Track feedback state per item
122
- const [feedbackState, setFeedbackState] = useState(new Map());
123
- // Resolve feedback config
124
- const feedbackConfig = useMemo(() => resolveFeedbackConfig(config.feedback), [config.feedback]);
125
- // Subscribe to context changes for reactive updates
126
- useEffect(() => {
127
- const unsubscribe = runtime.context.subscribe(() => {
128
- forceUpdate();
129
- });
130
- return unsubscribe;
131
- }, [runtime.context]);
132
- // Subscribe to accumulator changes for event_count-based triggerWhen
133
- useEffect(() => {
134
- if (!runtime.accumulator?.subscribe)
135
- return;
136
- return runtime.accumulator.subscribe(() => {
137
- forceUpdate();
138
- });
139
- }, [runtime.accumulator]);
140
- // Subscribe to faq:open:* events from overlay CTA clicks
141
- useEffect(() => {
142
- if (!runtime.events.subscribe)
143
- return;
144
- // Check EventBus history for pending faq:open events
145
- // (may have fired before this widget mounted, e.g. when canvas was closed)
146
- if (runtime.events.getRecent) {
147
- const recentEvents = runtime.events.getRecent({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, 10);
148
- const pendingEvent = recentEvents
149
- .filter((e) => {
150
- const actionId = e.props?.actionId;
151
- return typeof actionId === 'string' && actionId.startsWith('faq:open:');
152
- })
153
- .pop(); // Most recent
154
- if (pendingEvent && Date.now() - pendingEvent.ts < 10000) {
155
- const questionId = pendingEvent.props.actionId.replace('faq:open:', '');
156
- setExpandedIds(new Set([questionId]));
157
- // Scroll into view after render
158
- requestAnimationFrame(() => {
159
- const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
160
- if (el)
161
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
162
- });
163
- }
164
- }
165
- // Subscribe to future CTA events
166
- const unsubscribe = runtime.events.subscribe({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, (event) => {
167
- const actionId = event.props?.actionId;
168
- if (typeof actionId !== 'string' || !actionId.startsWith('faq:open:'))
169
- return;
170
- const questionId = actionId.replace('faq:open:', '');
171
- setExpandedIds(new Set([questionId]));
172
- // Scroll the question into view
173
- requestAnimationFrame(() => {
174
- const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
175
- if (el)
176
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
177
- });
178
- // Request canvas open (for future tile-based FAQ rendering)
179
- runtime.events.publish('canvas.requestOpen');
180
- });
181
- return unsubscribe;
182
- }, [runtime]);
183
- // Subscribe to notification.deep_link events (from insertHtml deepLink clicks + notification toasts)
184
- useEffect(() => {
185
- if (!runtime.events.subscribe)
186
- return;
187
- const handleDeepLink = (event) => {
188
- const tileId = event.props?.tileId;
189
- const itemId = event.props?.itemId;
190
- // Only handle if this deep link targets our tile
191
- if (tileId !== instanceId)
192
- return;
193
- if (!itemId)
194
- return;
195
- // Expand the target item
196
- setExpandedIds(new Set([itemId]));
197
- // Flash-highlight the item
198
- setHighlightId(itemId);
199
- setTimeout(() => setHighlightId(null), 1500);
200
- // Scroll into view after render
201
- requestAnimationFrame(() => {
202
- const el = document.querySelector(`[data-faq-item-id="${itemId}"]`);
203
- if (el)
204
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
205
- });
206
- };
207
- // Check recent events (may have fired before widget mounted, e.g. canvas was closed)
208
- if (runtime.events.getRecent) {
209
- const recent = runtime.events.getRecent({ names: ['notification.deep_link'] }, 5);
210
- const pending = recent
211
- .filter((e) => e.props?.tileId === instanceId && e.props?.itemId)
212
- .pop();
213
- if (pending && Date.now() - pending.ts < 10000) {
214
- handleDeepLink(pending);
215
- }
216
- }
217
- // Subscribe to future events
218
- const unsubscribe = runtime.events.subscribe({ names: ['notification.deep_link'] }, handleDeepLink);
219
- return unsubscribe;
220
- }, [runtime, instanceId]);
221
- // Filter visible questions based on per-item triggerWhen
222
- // biome-ignore lint/correctness/useExhaustiveDependencies: renderTick is intentionally included to force re-evaluation when the runtime's mutable context changes (subscribed above via forceUpdate)
223
- const visibleQuestions = useMemo(() => config.actions.filter((q) => {
224
- // No triggerWhen = always visible
225
- if (!q.triggerWhen)
226
- return true;
227
- // Evaluate the decision strategy
228
- const result = runtime.evaluateSync(q.triggerWhen);
229
- return result.value;
230
- }), [config.actions, runtime, renderTick]);
231
- // NOTE: faq:question_revealed is now published by useNotifyWatcher in
232
- // ShadowCanvasOverlay (always mounted), so it fires even with drawer closed.
233
- // Apply priority ordering
234
- const orderedQuestions = useMemo(() => {
235
- if (config.ordering === 'priority') {
236
- return [...visibleQuestions].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
237
- }
238
- // 'static' or undefined — preserve config order
239
- return visibleQuestions;
240
- }, [visibleQuestions, config.ordering]);
241
- // Apply search filter
242
- const filteredQuestions = useMemo(() => {
243
- if (!config.searchable || !searchQuery.trim()) {
244
- return orderedQuestions;
245
- }
246
- const query = searchQuery.toLowerCase();
247
- return orderedQuestions.filter((q) => q.config.question.toLowerCase().includes(query) ||
248
- getAnswerText(q.config.answer).toLowerCase().includes(query) ||
249
- q.config.category?.toLowerCase().includes(query));
250
- }, [orderedQuestions, searchQuery, config.searchable]);
251
- // Group by category
252
- const categoryGroups = useMemo(() => {
253
- const groups = new Map();
254
- for (const q of filteredQuestions) {
255
- const cat = q.config.category;
256
- if (!groups.has(cat)) {
257
- groups.set(cat, []);
258
- }
259
- groups.get(cat).push(q);
260
- }
261
- return groups;
262
- }, [filteredQuestions]);
263
- // Check if any items have categories
264
- const hasCategories = useMemo(() => filteredQuestions.some((q) => q.config.category), [filteredQuestions]);
265
- // Resolve theme (auto -> detect system preference)
266
- const resolvedTheme = useMemo(() => {
267
- if (config.theme && config.theme !== 'auto')
268
- return config.theme;
269
- // Check system preference (SSR-safe)
270
- if (typeof window !== 'undefined') {
271
- return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
272
- }
273
- return 'light';
274
- }, [config.theme]);
275
- // Handle question toggle
276
- const handleToggle = useCallback((id) => {
277
- setExpandedIds((prev) => {
278
- const next = new Set(prev);
279
- if (config.expandBehavior === 'single') {
280
- // Single mode: collapse all others
281
- if (prev.has(id)) {
282
- return new Set();
283
- }
284
- return new Set([id]);
285
- }
286
- // Multiple mode: toggle this one
287
- if (prev.has(id)) {
288
- next.delete(id);
289
- }
290
- else {
291
- next.add(id);
292
- }
293
- return next;
294
- });
295
- // Publish toggle event for analytics
296
- runtime.events.publish('faq:toggled', {
297
- instanceId,
298
- questionId: id,
299
- expanded: !expandedIds.has(id),
300
- timestamp: Date.now(),
301
- });
302
- }, [config.expandBehavior, runtime.events, instanceId, expandedIds]);
303
- // Handle feedback
304
- const handleFeedback = useCallback((itemId, question, value) => {
305
- setFeedbackState((prev) => {
306
- const next = new Map(prev);
307
- next.set(itemId, value);
308
- return next;
309
- });
310
- runtime.events.publish('faq:feedback', {
311
- itemId,
312
- question,
313
- value,
314
- });
315
- }, [runtime.events]);
316
- // Compute styles
317
- const containerStyle = {
318
- ...baseStyles.container,
319
- ...themeStyles[resolvedTheme].container,
320
- };
321
- const searchInputStyle = {
322
- ...baseStyles.searchInput,
323
- ...themeStyles[resolvedTheme].searchInput,
324
- };
325
- const emptyStateStyle = {
326
- ...baseStyles.emptyState,
327
- ...themeStyles[resolvedTheme].emptyState,
328
- };
329
- const categoryHeaderStyle = {
330
- ...baseStyles.categoryHeader,
331
- ...themeStyles[resolvedTheme].categoryHeader,
332
- };
333
- // Render a list of FAQ items
334
- 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)));
335
- // Empty state (no visible questions at all)
336
- if (visibleQuestions.length === 0) {
337
- 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." }) }));
338
- }
339
- 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
340
- ? 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')))
341
- : renderItems(filteredQuestions) }), config.searchable && filteredQuestions.length === 0 && searchQuery && (_jsxs("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ["No questions found matching \"", searchQuery, "\""] }))] }));
342
- }
343
- // ============================================================================
344
- // Mountable Widget Interface
345
- // ============================================================================
346
- /**
347
- * Mountable widget interface for the runtime's WidgetRegistry.
348
- */
349
- export const FAQMountableWidget = {
350
- mount(container, config) {
351
- const { runtime, instanceId = 'faq-widget', ...faqConfig } = config || {
352
- expandBehavior: 'single',
353
- searchable: false,
354
- theme: 'auto',
355
- actions: [],
356
- };
357
- // React rendering when runtime + ReactDOM are available
358
- if (runtime && typeof createRoot === 'function') {
359
- const root = createRoot(container);
360
- root.render(React.createElement(FAQWidget, {
361
- config: faqConfig,
362
- runtime: runtime,
363
- instanceId,
364
- }));
365
- return () => {
366
- root.unmount();
367
- };
368
- }
369
- // HTML fallback when React is not available
370
- const questions = faqConfig.actions || [];
371
- container.innerHTML = `
372
- <div style="font-family: system-ui; max-width: 800px;">
373
- ${questions
374
- .map((q) => `
375
- <div style="margin-bottom: 8px; padding: 16px; background: ${slateGrey[12]}; border-radius: 8px;">
376
- <strong>${q.config.question}</strong>
377
- <p style="margin-top: 8px; color: ${slateGrey[6]};">${getAnswerText(q.config.answer)}</p>
378
- </div>
379
- `)
380
- .join('')}
381
- </div>
382
- `;
383
- return () => {
384
- container.innerHTML = '';
385
- };
386
- },
387
- };
388
- 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("./faq-types").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;;;;;;;;;;;;;;;;;;;;2BAoC4jiB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAtBtliB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;CAQjD,CAAC;AAaF,eAAe,QAAQ,CAAC"}
package/dist/cdn.js DELETED
@@ -1,46 +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, { editorPanel } from './editor';
8
- import { runtime } from './runtime';
9
- /**
10
- * App manifest for registry registration.
11
- * Follows the AppManifest interface expected by AppLoader/AppRegistry.
12
- */
13
- export const manifest = {
14
- id: 'adaptive-faq',
15
- version: runtime.version,
16
- name: runtime.name,
17
- description: runtime.description,
18
- // biome-ignore lint: manifest shape maps runtime fields to specific interface properties
19
- runtime: {
20
- actions: runtime.executors,
21
- widgets: runtime.widgets,
22
- notifyWatchers: runtime.notifyWatchers,
23
- },
24
- editor: {
25
- component: FAQEditor,
26
- panel: editorPanel,
27
- getActionLabel(action) {
28
- const config = action.config || {};
29
- return config.question || action.kind || 'faq:update';
30
- },
31
- },
32
- metadata: {
33
- isBuiltIn: false,
34
- },
35
- };
36
- /**
37
- * Self-register with global registry if available.
38
- * This happens when loaded via script tag (UMD).
39
- */
40
- if (typeof window !== 'undefined') {
41
- const registry = window.SynOS?.appRegistry;
42
- if (registry && typeof registry.register === 'function') {
43
- registry.register(manifest);
44
- }
45
- }
46
- export default manifest;
package/dist/executors.js DELETED
@@ -1,150 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Action Executors
3
- *
4
- * Three executors that operate on the FAQStore:
5
- * - executeScrollToFaq: scroll to a FAQ item and optionally expand it
6
- * - executeToggleFaqItem: open / close / toggle a FAQ item
7
- * - executeUpdateFaq: add, remove, reorder, or replace FAQ items
8
- */
9
- // ============================================================================
10
- // Helpers
11
- // ============================================================================
12
- /**
13
- * Resolve a FAQ item by direct ID or by fuzzy question text match.
14
- * Throws if neither yields a result.
15
- */
16
- function resolveItem(store, itemId, itemQuestion) {
17
- if (itemId) {
18
- const found = store.getState().items.find((i) => i.config.id === itemId);
19
- if (found)
20
- return found;
21
- }
22
- if (itemQuestion) {
23
- const found = store.findByQuestion(itemQuestion);
24
- if (found)
25
- return found;
26
- }
27
- throw new Error('FAQ item not found');
28
- }
29
- // ============================================================================
30
- // executeScrollToFaq
31
- // ============================================================================
32
- /**
33
- * Scroll to a FAQ item in the DOM and optionally expand it.
34
- *
35
- * Looks up the item by `itemId` or `itemQuestion`, scrolls the matching
36
- * `[data-faq-item-id]` element into view, and expands it unless
37
- * `expand` is explicitly set to `false`.
38
- */
39
- export async function executeScrollToFaq(action, context, store) {
40
- const item = resolveItem(store, action.itemId, action.itemQuestion);
41
- const { id } = item.config;
42
- // Expand the item unless explicitly told not to
43
- if (action.expand !== false) {
44
- store.expand(id);
45
- }
46
- // Scroll the DOM element into view (may be absent in test environments)
47
- const el = document.querySelector(`[data-faq-item-id="${id}"]`);
48
- if (el) {
49
- el.scrollIntoView({
50
- behavior: action.behavior ?? 'smooth',
51
- });
52
- }
53
- // Publish analytics event
54
- context.publishEvent('faq:scroll_to', { itemId: id });
55
- return {
56
- cleanup: () => {
57
- // Optionally collapse on revert
58
- },
59
- };
60
- }
61
- // ============================================================================
62
- // executeToggleFaqItem
63
- // ============================================================================
64
- /**
65
- * Open, close, or toggle a FAQ item's expanded state.
66
- */
67
- export async function executeToggleFaqItem(action, context, store) {
68
- const item = resolveItem(store, action.itemId, action.itemQuestion);
69
- const { id } = item.config;
70
- const desiredState = action.state ?? 'toggle';
71
- let newState;
72
- switch (desiredState) {
73
- case 'open':
74
- store.expand(id);
75
- newState = 'open';
76
- break;
77
- case 'closed':
78
- store.collapse(id);
79
- newState = 'closed';
80
- break;
81
- default: {
82
- const wasExpanded = store.getState().expandedItems.has(id);
83
- store.toggle(id);
84
- newState = wasExpanded ? 'closed' : 'open';
85
- break;
86
- }
87
- }
88
- context.publishEvent('faq:toggle', { itemId: id, newState });
89
- return {
90
- cleanup: () => {
91
- // Revert toggle on cleanup
92
- },
93
- };
94
- }
95
- // ============================================================================
96
- // executeUpdateFaq
97
- // ============================================================================
98
- /**
99
- * Add, remove, reorder, or replace FAQ items in the store.
100
- */
101
- export async function executeUpdateFaq(action, context, store) {
102
- switch (action.operation) {
103
- case 'add': {
104
- const items = action.items ?? [];
105
- const position = action.position === 'prepend' ? 'prepend' : 'append';
106
- store.addItems(items, position);
107
- break;
108
- }
109
- case 'remove': {
110
- if (!action.itemId) {
111
- throw new Error('FAQ item not found');
112
- }
113
- // Verify the item exists before removing
114
- const exists = store.getState().items.some((i) => i.config.id === action.itemId);
115
- if (!exists) {
116
- throw new Error('FAQ item not found');
117
- }
118
- store.removeItem(action.itemId);
119
- break;
120
- }
121
- case 'reorder': {
122
- const order = action.order ?? [];
123
- store.reorderItems(order);
124
- break;
125
- }
126
- case 'replace': {
127
- const items = action.items ?? [];
128
- store.replaceItems(items);
129
- break;
130
- }
131
- }
132
- context.publishEvent('faq:update', { operation: action.operation });
133
- return {
134
- cleanup: () => {
135
- // Could snapshot previous state for undo
136
- },
137
- };
138
- }
139
- // ============================================================================
140
- // Executor Definitions for Registration
141
- // ============================================================================
142
- /**
143
- * All executors provided by adaptive-faq.
144
- * These are registered with the runtime's ExecutorRegistry.
145
- */
146
- export const executorDefinitions = [
147
- { kind: 'faq:scroll_to', executor: executeScrollToFaq },
148
- { kind: 'faq:toggle_item', executor: executeToggleFaqItem },
149
- { kind: 'faq:update', executor: executeUpdateFaq },
150
- ];