@syntrologie/adapt-faq 0.0.0-semantically-released → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FAQWidget.d.ts +22 -1
- package/dist/FAQWidget.d.ts.map +1 -1
- package/dist/FAQWidget.js +182 -49
- package/dist/cdn.d.ts +20 -2
- package/dist/cdn.d.ts.map +1 -1
- package/dist/cdn.js +7 -6
- package/dist/editor.d.ts +12 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +427 -162
- package/dist/executors.d.ts +41 -0
- package/dist/executors.d.ts.map +1 -0
- package/dist/executors.js +151 -0
- package/dist/runtime.d.ts +16 -8
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +10 -10
- package/dist/schema.d.ts +2188 -143
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +121 -2
- package/dist/state.d.ts +28 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +132 -0
- package/dist/summarize.d.ts +14 -0
- package/dist/summarize.d.ts.map +1 -0
- package/dist/summarize.js +62 -0
- package/dist/types.d.ts +109 -56
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -1
- package/package.json +13 -5
package/dist/FAQWidget.d.ts
CHANGED
|
@@ -7,7 +7,28 @@
|
|
|
7
7
|
* Demonstrates the compositional action pattern where child actions
|
|
8
8
|
* (faq:question) serve as configuration data for the parent widget.
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
10
|
+
import type { FAQConfig, DecisionStrategy } from './types';
|
|
11
|
+
export interface FAQWidgetRuntime {
|
|
12
|
+
evaluateSync: <T>(strategy: DecisionStrategy<T>) => {
|
|
13
|
+
value: T;
|
|
14
|
+
isFallback: boolean;
|
|
15
|
+
};
|
|
16
|
+
context: {
|
|
17
|
+
subscribe: (callback: () => void) => () => void;
|
|
18
|
+
};
|
|
19
|
+
events: {
|
|
20
|
+
publish: (name: string, props?: Record<string, unknown>) => void;
|
|
21
|
+
};
|
|
22
|
+
state?: {
|
|
23
|
+
get: (key: string) => unknown;
|
|
24
|
+
set: (key: string, value: unknown) => void;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
interface FAQWidgetProps {
|
|
28
|
+
config: FAQConfig;
|
|
29
|
+
runtime: FAQWidgetRuntime;
|
|
30
|
+
instanceId: string;
|
|
31
|
+
}
|
|
11
32
|
/**
|
|
12
33
|
* FAQWidget - Renders a collapsible Q&A accordion with per-item activation.
|
|
13
34
|
*
|
package/dist/FAQWidget.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FAQWidget.d.ts","sourceRoot":"","sources":["../src/FAQWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"FAQWidget.d.ts","sourceRoot":"","sources":["../src/FAQWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAEV,SAAS,EAIT,gBAAgB,EACjB,MAAM,SAAS,CAAC;AA0CjB,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK;QAAE,KAAK,EAAE,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IACtF,OAAO,EAAE;QAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;KAAE,CAAC;IAC7D,MAAM,EAAE;QAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KAAE,CAAC;IAC7E,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC;CACvF;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AA4SD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,cAAc,2CAqOxE;AAMD;;GAEG;AACH,eAAO,MAAM,kBAAkB;qBAEhB,WAAW,WACb,SAAS,GAAG;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CA+C3E,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
package/dist/FAQWidget.js
CHANGED
|
@@ -8,7 +8,43 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* Demonstrates the compositional action pattern where child actions
|
|
9
9
|
* (faq:question) serve as configuration data for the parent widget.
|
|
10
10
|
*/
|
|
11
|
-
import { useEffect, useReducer, useMemo, useCallback, useState } from 'react';
|
|
11
|
+
import React, { useEffect, useReducer, useMemo, useCallback, useState } from 'react';
|
|
12
|
+
import { createRoot } from 'react-dom/client';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Helpers
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/** Extract plain text from an FAQAnswer for search matching */
|
|
17
|
+
function getAnswerText(answer) {
|
|
18
|
+
if (typeof answer === 'string')
|
|
19
|
+
return answer;
|
|
20
|
+
if (answer.type === 'rich')
|
|
21
|
+
return answer.html;
|
|
22
|
+
return answer.content;
|
|
23
|
+
}
|
|
24
|
+
/** Render an FAQAnswer based on its type */
|
|
25
|
+
function renderAnswer(answer) {
|
|
26
|
+
if (typeof answer === 'string') {
|
|
27
|
+
return _jsx("p", { style: { margin: 0 }, children: answer });
|
|
28
|
+
}
|
|
29
|
+
if (answer.type === 'rich') {
|
|
30
|
+
return _jsx("div", { style: { margin: 0 }, dangerouslySetInnerHTML: { __html: answer.html } });
|
|
31
|
+
}
|
|
32
|
+
// markdown — render content as text (full markdown rendering is a future enhancement)
|
|
33
|
+
return _jsx("p", { style: { margin: 0 }, children: answer.content });
|
|
34
|
+
}
|
|
35
|
+
/** Resolve feedback config into a normalized FeedbackConfig or null */
|
|
36
|
+
function resolveFeedbackConfig(feedback) {
|
|
37
|
+
if (!feedback)
|
|
38
|
+
return null;
|
|
39
|
+
if (feedback === true) {
|
|
40
|
+
return { style: 'thumbs' };
|
|
41
|
+
}
|
|
42
|
+
return feedback;
|
|
43
|
+
}
|
|
44
|
+
/** Get the feedback prompt text */
|
|
45
|
+
function getFeedbackPrompt(feedbackConfig) {
|
|
46
|
+
return feedbackConfig.prompt || 'Was this helpful?';
|
|
47
|
+
}
|
|
12
48
|
// ============================================================================
|
|
13
49
|
// Styles
|
|
14
50
|
// ============================================================================
|
|
@@ -73,6 +109,36 @@ const baseStyles = {
|
|
|
73
109
|
borderRadius: '4px',
|
|
74
110
|
marginBottom: '8px',
|
|
75
111
|
},
|
|
112
|
+
categoryHeader: {
|
|
113
|
+
fontSize: '13px',
|
|
114
|
+
fontWeight: 700,
|
|
115
|
+
textTransform: 'uppercase',
|
|
116
|
+
letterSpacing: '0.05em',
|
|
117
|
+
padding: '12px 4px 6px 4px',
|
|
118
|
+
marginTop: '8px',
|
|
119
|
+
},
|
|
120
|
+
feedback: {
|
|
121
|
+
display: 'flex',
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
gap: '8px',
|
|
124
|
+
marginTop: '12px',
|
|
125
|
+
paddingTop: '10px',
|
|
126
|
+
borderTop: '1px solid rgba(0, 0, 0, 0.08)',
|
|
127
|
+
fontSize: '13px',
|
|
128
|
+
},
|
|
129
|
+
feedbackButton: {
|
|
130
|
+
background: 'none',
|
|
131
|
+
border: '1px solid transparent',
|
|
132
|
+
cursor: 'pointer',
|
|
133
|
+
fontSize: '16px',
|
|
134
|
+
padding: '4px 8px',
|
|
135
|
+
borderRadius: '4px',
|
|
136
|
+
transition: 'background-color 0.15s ease, border-color 0.15s ease',
|
|
137
|
+
},
|
|
138
|
+
feedbackButtonSelected: {
|
|
139
|
+
borderColor: 'rgba(0, 0, 0, 0.2)',
|
|
140
|
+
backgroundColor: 'rgba(0, 0, 0, 0.04)',
|
|
141
|
+
},
|
|
76
142
|
emptyState: {
|
|
77
143
|
textAlign: 'center',
|
|
78
144
|
padding: '48px 24px',
|
|
@@ -116,9 +182,15 @@ const themeStyles = {
|
|
|
116
182
|
backgroundColor: '#e0e7ff',
|
|
117
183
|
color: '#4338ca',
|
|
118
184
|
},
|
|
185
|
+
categoryHeader: {
|
|
186
|
+
color: '#6b7280',
|
|
187
|
+
},
|
|
119
188
|
emptyState: {
|
|
120
189
|
color: '#9ca3af',
|
|
121
190
|
},
|
|
191
|
+
feedbackPrompt: {
|
|
192
|
+
color: '#6b7280',
|
|
193
|
+
},
|
|
122
194
|
},
|
|
123
195
|
dark: {
|
|
124
196
|
container: {
|
|
@@ -151,15 +223,21 @@ const themeStyles = {
|
|
|
151
223
|
backgroundColor: '#312e81',
|
|
152
224
|
color: '#a5b4fc',
|
|
153
225
|
},
|
|
226
|
+
categoryHeader: {
|
|
227
|
+
color: '#9ca3af',
|
|
228
|
+
},
|
|
154
229
|
emptyState: {
|
|
155
230
|
color: '#6b7280',
|
|
156
231
|
},
|
|
232
|
+
feedbackPrompt: {
|
|
233
|
+
color: '#9ca3af',
|
|
234
|
+
},
|
|
157
235
|
},
|
|
158
236
|
};
|
|
159
|
-
function FAQItem({ item, isExpanded, onToggle, theme }) {
|
|
237
|
+
function FAQItem({ item, isExpanded, onToggle, theme, feedbackConfig, feedbackValue, onFeedback, }) {
|
|
160
238
|
const [isHovered, setIsHovered] = useState(false);
|
|
161
239
|
const colors = themeStyles[theme];
|
|
162
|
-
const { question, answer
|
|
240
|
+
const { question, answer } = item.config;
|
|
163
241
|
const itemStyle = {
|
|
164
242
|
...baseStyles.item,
|
|
165
243
|
...colors.item,
|
|
@@ -180,11 +258,17 @@ function FAQItem({ item, isExpanded, onToggle, theme }) {
|
|
|
180
258
|
maxHeight: isExpanded ? '500px' : '0',
|
|
181
259
|
paddingBottom: isExpanded ? '16px' : '0',
|
|
182
260
|
};
|
|
183
|
-
const
|
|
184
|
-
...baseStyles.
|
|
185
|
-
...colors.
|
|
261
|
+
const feedbackStyle = {
|
|
262
|
+
...baseStyles.feedback,
|
|
263
|
+
...colors.feedbackPrompt,
|
|
186
264
|
};
|
|
187
|
-
return (_jsxs("div", { style: itemStyle, children: [_jsxs("button", { style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx("span", { children: question }), _jsx("span", { style: chevronStyle, children:
|
|
265
|
+
return (_jsxs("div", { style: itemStyle, "data-faq-item-id": item.config.id, children: [_jsxs("button", { style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx("span", { children: question }), _jsx("span", { style: chevronStyle, children: '\u25BC' })] }), _jsxs("div", { style: answerStyle, "aria-hidden": !isExpanded, children: [renderAnswer(answer), isExpanded && feedbackConfig && (_jsxs("div", { style: feedbackStyle, children: [_jsx("span", { children: getFeedbackPrompt(feedbackConfig) }), _jsx("button", { style: {
|
|
266
|
+
...baseStyles.feedbackButton,
|
|
267
|
+
...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),
|
|
268
|
+
}, "aria-label": "Thumbs up", onClick: () => onFeedback(item.config.id, question, 'up'), children: '\uD83D\uDC4D' }), _jsx("button", { style: {
|
|
269
|
+
...baseStyles.feedbackButton,
|
|
270
|
+
...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),
|
|
271
|
+
}, "aria-label": "Thumbs down", onClick: () => onFeedback(item.config.id, question, 'down'), children: '\uD83D\uDC4E' })] }))] })] }));
|
|
188
272
|
}
|
|
189
273
|
// ============================================================================
|
|
190
274
|
// FAQWidget Component
|
|
@@ -205,6 +289,10 @@ export function FAQWidget({ config, runtime, instanceId }) {
|
|
|
205
289
|
const [expandedIds, setExpandedIds] = useState(new Set());
|
|
206
290
|
// Search query state
|
|
207
291
|
const [searchQuery, setSearchQuery] = useState('');
|
|
292
|
+
// Track feedback state per item
|
|
293
|
+
const [feedbackState, setFeedbackState] = useState(new Map());
|
|
294
|
+
// Resolve feedback config
|
|
295
|
+
const feedbackConfig = useMemo(() => resolveFeedbackConfig(config.feedback), [config.feedback]);
|
|
208
296
|
// Subscribe to context changes for reactive updates
|
|
209
297
|
useEffect(() => {
|
|
210
298
|
const unsubscribe = runtime.context.subscribe(() => {
|
|
@@ -213,27 +301,47 @@ export function FAQWidget({ config, runtime, instanceId }) {
|
|
|
213
301
|
return unsubscribe;
|
|
214
302
|
}, [runtime.context]);
|
|
215
303
|
// Filter visible questions based on per-item showWhen
|
|
216
|
-
const visibleQuestions = useMemo(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
304
|
+
const visibleQuestions = useMemo(() => config.actions.filter((q) => {
|
|
305
|
+
// No showWhen = always visible
|
|
306
|
+
if (!q.showWhen)
|
|
307
|
+
return true;
|
|
308
|
+
// Evaluate the decision strategy
|
|
309
|
+
const result = runtime.evaluateSync(q.showWhen);
|
|
310
|
+
return result.value;
|
|
311
|
+
}), [config.actions, runtime]);
|
|
312
|
+
// Apply priority ordering
|
|
313
|
+
const orderedQuestions = useMemo(() => {
|
|
314
|
+
if (config.ordering === 'priority') {
|
|
315
|
+
return [...visibleQuestions].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
|
|
316
|
+
}
|
|
317
|
+
// 'static' or undefined — preserve config order
|
|
318
|
+
return visibleQuestions;
|
|
319
|
+
}, [visibleQuestions, config.ordering]);
|
|
226
320
|
// Apply search filter
|
|
227
321
|
const filteredQuestions = useMemo(() => {
|
|
228
322
|
if (!config.searchable || !searchQuery.trim()) {
|
|
229
|
-
return
|
|
323
|
+
return orderedQuestions;
|
|
230
324
|
}
|
|
231
325
|
const query = searchQuery.toLowerCase();
|
|
232
|
-
return
|
|
233
|
-
q.config.answer.toLowerCase().includes(query) ||
|
|
326
|
+
return orderedQuestions.filter((q) => q.config.question.toLowerCase().includes(query) ||
|
|
327
|
+
getAnswerText(q.config.answer).toLowerCase().includes(query) ||
|
|
234
328
|
q.config.category?.toLowerCase().includes(query));
|
|
235
|
-
}, [
|
|
236
|
-
//
|
|
329
|
+
}, [orderedQuestions, searchQuery, config.searchable]);
|
|
330
|
+
// Group by category
|
|
331
|
+
const categoryGroups = useMemo(() => {
|
|
332
|
+
const groups = new Map();
|
|
333
|
+
for (const q of filteredQuestions) {
|
|
334
|
+
const cat = q.config.category;
|
|
335
|
+
if (!groups.has(cat)) {
|
|
336
|
+
groups.set(cat, []);
|
|
337
|
+
}
|
|
338
|
+
groups.get(cat).push(q);
|
|
339
|
+
}
|
|
340
|
+
return groups;
|
|
341
|
+
}, [filteredQuestions]);
|
|
342
|
+
// Check if any items have categories
|
|
343
|
+
const hasCategories = useMemo(() => filteredQuestions.some((q) => q.config.category), [filteredQuestions]);
|
|
344
|
+
// Resolve theme (auto -> detect system preference)
|
|
237
345
|
const resolvedTheme = useMemo(() => {
|
|
238
346
|
if (config.theme !== 'auto')
|
|
239
347
|
return config.theme;
|
|
@@ -254,16 +362,14 @@ export function FAQWidget({ config, runtime, instanceId }) {
|
|
|
254
362
|
}
|
|
255
363
|
return new Set([id]);
|
|
256
364
|
}
|
|
365
|
+
// Multiple mode: toggle this one
|
|
366
|
+
if (prev.has(id)) {
|
|
367
|
+
next.delete(id);
|
|
368
|
+
}
|
|
257
369
|
else {
|
|
258
|
-
|
|
259
|
-
if (prev.has(id)) {
|
|
260
|
-
next.delete(id);
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
next.add(id);
|
|
264
|
-
}
|
|
265
|
-
return next;
|
|
370
|
+
next.add(id);
|
|
266
371
|
}
|
|
372
|
+
return next;
|
|
267
373
|
});
|
|
268
374
|
// Publish toggle event for analytics
|
|
269
375
|
runtime.events.publish('faq:toggled', {
|
|
@@ -273,6 +379,19 @@ export function FAQWidget({ config, runtime, instanceId }) {
|
|
|
273
379
|
timestamp: Date.now(),
|
|
274
380
|
});
|
|
275
381
|
}, [config.expandBehavior, runtime.events, instanceId, expandedIds]);
|
|
382
|
+
// Handle feedback
|
|
383
|
+
const handleFeedback = useCallback((itemId, question, value) => {
|
|
384
|
+
setFeedbackState((prev) => {
|
|
385
|
+
const next = new Map(prev);
|
|
386
|
+
next.set(itemId, value);
|
|
387
|
+
return next;
|
|
388
|
+
});
|
|
389
|
+
runtime.events.publish('faq:feedback', {
|
|
390
|
+
itemId,
|
|
391
|
+
question,
|
|
392
|
+
value,
|
|
393
|
+
});
|
|
394
|
+
}, [runtime.events]);
|
|
276
395
|
// Compute styles
|
|
277
396
|
const containerStyle = {
|
|
278
397
|
...baseStyles.container,
|
|
@@ -286,11 +405,19 @@ export function FAQWidget({ config, runtime, instanceId }) {
|
|
|
286
405
|
...baseStyles.emptyState,
|
|
287
406
|
...themeStyles[resolvedTheme].emptyState,
|
|
288
407
|
};
|
|
408
|
+
const categoryHeaderStyle = {
|
|
409
|
+
...baseStyles.categoryHeader,
|
|
410
|
+
...themeStyles[resolvedTheme].categoryHeader,
|
|
411
|
+
};
|
|
412
|
+
// Render a list of FAQ items
|
|
413
|
+
const renderItems = (items) => items.map((q) => (_jsx(FAQItem, { item: q, isExpanded: expandedIds.has(q.config.id), onToggle: () => handleToggle(q.config.id), theme: resolvedTheme, feedbackConfig: feedbackConfig, feedbackValue: feedbackState.get(q.config.id), onFeedback: handleFeedback }, q.config.id)));
|
|
289
414
|
// Empty state (no visible questions at all)
|
|
290
415
|
if (visibleQuestions.length === 0) {
|
|
291
416
|
return (_jsx("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: _jsx("div", { style: emptyStateStyle, children: "No FAQ questions available." }) }));
|
|
292
417
|
}
|
|
293
|
-
return (_jsxs("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && (_jsx("div", { style: baseStyles.searchWrapper, children: _jsx("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle }) })), _jsx("div", { style: baseStyles.accordion, children:
|
|
418
|
+
return (_jsxs("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && (_jsx("div", { style: baseStyles.searchWrapper, children: _jsx("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle }) })), _jsx("div", { style: baseStyles.accordion, children: hasCategories
|
|
419
|
+
? 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')))
|
|
420
|
+
: renderItems(filteredQuestions) }), config.searchable && filteredQuestions.length === 0 && searchQuery && (_jsxs("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ["No questions found matching \"", searchQuery, "\""] }))] }));
|
|
294
421
|
}
|
|
295
422
|
// ============================================================================
|
|
296
423
|
// Mountable Widget Interface
|
|
@@ -300,30 +427,36 @@ export function FAQWidget({ config, runtime, instanceId }) {
|
|
|
300
427
|
*/
|
|
301
428
|
export const FAQMountableWidget = {
|
|
302
429
|
mount(container, config) {
|
|
303
|
-
|
|
304
|
-
// In practice, the runtime handles React rendering
|
|
305
|
-
const { runtime, instanceId: _instanceId = 'faq-widget', ...faqConfig } = config || {
|
|
430
|
+
const { runtime, instanceId = 'faq-widget', ...faqConfig } = config || {
|
|
306
431
|
expandBehavior: 'single',
|
|
307
432
|
searchable: false,
|
|
308
433
|
theme: 'auto',
|
|
309
434
|
actions: [],
|
|
310
435
|
};
|
|
311
|
-
//
|
|
312
|
-
if (
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
<p style="margin-top: 8px; color: #4b5563;">${q.config.answer}</p>
|
|
321
|
-
</div>
|
|
322
|
-
`)
|
|
323
|
-
.join('')}
|
|
324
|
-
</div>
|
|
325
|
-
`;
|
|
436
|
+
// React rendering when runtime + ReactDOM are available
|
|
437
|
+
if (runtime && typeof createRoot === 'function') {
|
|
438
|
+
const root = createRoot(container);
|
|
439
|
+
root.render(React.createElement(FAQWidget, {
|
|
440
|
+
config: faqConfig,
|
|
441
|
+
runtime: runtime,
|
|
442
|
+
instanceId,
|
|
443
|
+
}));
|
|
444
|
+
return () => { root.unmount(); };
|
|
326
445
|
}
|
|
446
|
+
// HTML fallback for non-React environments
|
|
447
|
+
const questions = faqConfig.actions || [];
|
|
448
|
+
container.innerHTML = `
|
|
449
|
+
<div style="font-family: system-ui; max-width: 800px;">
|
|
450
|
+
${questions
|
|
451
|
+
.map((q) => `
|
|
452
|
+
<div style="margin-bottom: 8px; padding: 16px; background: #f9fafb; border-radius: 8px;">
|
|
453
|
+
<strong>${q.config.question}</strong>
|
|
454
|
+
<p style="margin-top: 8px; color: #4b5563;">${getAnswerText(q.config.answer)}</p>
|
|
455
|
+
</div>
|
|
456
|
+
`)
|
|
457
|
+
.join('')}
|
|
458
|
+
</div>
|
|
459
|
+
`;
|
|
327
460
|
return () => {
|
|
328
461
|
container.innerHTML = '';
|
|
329
462
|
};
|
package/dist/cdn.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This module is bundled for CDN delivery and self-registers with the global
|
|
5
5
|
* SynOS app registry when loaded dynamically via the AppLoader.
|
|
6
6
|
*/
|
|
7
|
+
import FAQEditor from './editor';
|
|
7
8
|
/**
|
|
8
9
|
* App manifest for registry registration.
|
|
9
10
|
* Follows the AppManifest interface expected by AppLoader/AppRegistry.
|
|
@@ -14,12 +15,21 @@ export declare const manifest: {
|
|
|
14
15
|
name: string;
|
|
15
16
|
description: string;
|
|
16
17
|
runtime: {
|
|
17
|
-
actions:
|
|
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
|
+
}];
|
|
18
28
|
widgets: {
|
|
19
29
|
id: string;
|
|
20
30
|
component: {
|
|
21
31
|
mount(container: HTMLElement, config?: import("./types").FAQConfig & {
|
|
22
|
-
runtime?: import("./
|
|
32
|
+
runtime?: import("./FAQWidget").FAQWidgetRuntime;
|
|
23
33
|
instanceId?: string;
|
|
24
34
|
}): () => void;
|
|
25
35
|
};
|
|
@@ -30,6 +40,14 @@ export declare const manifest: {
|
|
|
30
40
|
};
|
|
31
41
|
}[];
|
|
32
42
|
};
|
|
43
|
+
editor: {
|
|
44
|
+
component: typeof FAQEditor;
|
|
45
|
+
panel: {
|
|
46
|
+
title: string;
|
|
47
|
+
icon: string;
|
|
48
|
+
description: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
33
51
|
metadata: {
|
|
34
52
|
isBuiltIn: boolean;
|
|
35
53
|
};
|
package/dist/cdn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
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;;;;;;;;;;;;;;;;;;;;2BA2B8kgB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;CAdlogB,CAAC;AAaF,eAAe,QAAQ,CAAC"}
|
package/dist/cdn.js
CHANGED
|
@@ -4,21 +4,22 @@
|
|
|
4
4
|
* This module is bundled for CDN delivery and self-registers with the global
|
|
5
5
|
* SynOS app registry when loaded dynamically via the AppLoader.
|
|
6
6
|
*/
|
|
7
|
+
import FAQEditor, { editorPanel } from './editor';
|
|
7
8
|
import { runtime } from './runtime';
|
|
8
9
|
/**
|
|
9
10
|
* App manifest for registry registration.
|
|
10
11
|
* Follows the AppManifest interface expected by AppLoader/AppRegistry.
|
|
11
12
|
*/
|
|
12
13
|
export const manifest = {
|
|
13
|
-
id: 'faq',
|
|
14
|
+
id: 'adaptive-faq',
|
|
14
15
|
version: runtime.version,
|
|
15
16
|
name: runtime.name,
|
|
16
17
|
description: runtime.description,
|
|
17
18
|
runtime: {
|
|
18
|
-
|
|
19
|
-
actions: [],
|
|
19
|
+
actions: runtime.executors,
|
|
20
20
|
widgets: runtime.widgets,
|
|
21
21
|
},
|
|
22
|
+
editor: { component: FAQEditor, panel: editorPanel },
|
|
22
23
|
metadata: {
|
|
23
24
|
isBuiltIn: false,
|
|
24
25
|
},
|
|
@@ -28,9 +29,9 @@ export const manifest = {
|
|
|
28
29
|
* This happens when loaded via script tag (UMD).
|
|
29
30
|
*/
|
|
30
31
|
if (typeof window !== 'undefined') {
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
|
|
32
|
+
const registry = window.SynOS?.appRegistry;
|
|
33
|
+
if (registry && typeof registry.register === 'function') {
|
|
34
|
+
registry.register(manifest);
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
export default manifest;
|
package/dist/editor.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adaptive FAQ - Editor Component
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Review & tweak editor for AI-generated FAQ question decisions.
|
|
5
|
+
* Displays a scannable list of Q&A cards with trigger, rationale,
|
|
6
|
+
* and inline editing. Includes detection badges and hover-to-highlight.
|
|
5
7
|
*/
|
|
6
|
-
import type
|
|
8
|
+
import { type EditorPanelProps } from './types';
|
|
7
9
|
export declare function FAQEditor({ config, onChange, editor }: EditorPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
8
10
|
/**
|
|
9
11
|
* Editor panel configuration for the app registry.
|
|
@@ -13,5 +15,13 @@ export declare const editorPanel: {
|
|
|
13
15
|
icon: string;
|
|
14
16
|
description: string;
|
|
15
17
|
};
|
|
18
|
+
export declare const editor: {
|
|
19
|
+
panel: {
|
|
20
|
+
title: string;
|
|
21
|
+
icon: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
component: typeof FAQEditor;
|
|
25
|
+
};
|
|
16
26
|
export default FAQEditor;
|
|
17
27
|
//# sourceMappingURL=editor.d.ts.map
|
package/dist/editor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,SAAS,CAAC;AAgbjB,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CA0WvE;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;CAIvB,CAAC;AAEF,eAAO,MAAM,MAAM;;;;;;;CAGlB,CAAC;AAEF,eAAe,SAAS,CAAC"}
|