@syntrologie/adapt-faq 2.16.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editor.d.ts CHANGED
@@ -1,40 +1,36 @@
1
1
  /**
2
2
  * Adaptive FAQ - Editor Component
3
3
  *
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.
4
+ * Lit web component that displays FAQ question cards with inline editing,
5
+ * detection, dismiss/restore, and rationale display.
6
+ *
7
+ * Custom events:
8
+ * navigate-home — user clicked back
9
+ * dirty-change — { dirty: boolean }
7
10
  */
8
- import { type EditorPanelProps, type FAQConfig, type FAQQuestionAction } from './types';
9
- export interface FlatItem {
10
- key: string;
11
- index: number;
12
- summary: string;
13
- trigger: string;
14
- rationale?: {
15
- why: string;
16
- confidence?: number;
11
+ import { LitElement } from 'lit';
12
+ import { type FAQConfig } from './types';
13
+ export declare class FAQEditorLit extends LitElement {
14
+ static properties: {
15
+ config: {
16
+ attribute: boolean;
17
+ };
18
+ onChange: {
19
+ attribute: boolean;
20
+ };
21
+ _editingKey: {
22
+ state: boolean;
23
+ };
17
24
  };
18
- firstAnchor: string | null;
19
- question: FAQQuestionAction;
25
+ config: FAQConfig | null;
26
+ onChange: ((updated: Record<string, unknown>) => void) | null;
27
+ private _editingKey;
28
+ createRenderRoot(): this;
29
+ private _handleBack;
30
+ private _handleItemClick;
31
+ private _handleFieldChange;
32
+ private _renderEditMode;
33
+ private _renderListMode;
34
+ render(): import("lit-html").TemplateResult<1>;
20
35
  }
21
- export declare function flattenItems(config: FAQConfig): FlatItem[];
22
- export declare function FAQEditor({ config, onChange, editor }: EditorPanelProps): import("react/jsx-runtime").JSX.Element;
23
- /**
24
- * Editor panel configuration for the app registry.
25
- */
26
- export declare const editorPanel: {
27
- title: string;
28
- icon: string;
29
- description: string;
30
- };
31
- export declare const editor: {
32
- panel: {
33
- title: string;
34
- icon: string;
35
- description: string;
36
- };
37
- component: typeof FAQEditor;
38
- };
39
- export default FAQEditor;
40
36
  //# sourceMappingURL=editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,SAAS,EACd,KAAK,iBAAiB,EAGvB,MAAM,SAAS,CAAC;AA6EjB,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,EAAE,CAW1D;AAwFD,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CA6WvE;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;CAIvB,CAAC;AAEF,eAAO,MAAM,MAAM;;;;;;;CAGlB,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAQ,UAAU,EAAW,MAAM,KAAK,CAAC;AAGhD,OAAO,EAAkB,KAAK,SAAS,EAAuC,MAAM,SAAS,CAAC;AAqC9F,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,UAAU;;;;;;;;;;MAIxB;IAEF,MAAM,EAAE,SAAS,GAAG,IAAI,CAAQ;IAChC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAErE,OAAO,CAAC,WAAW,CAAuB;IAEjC,gBAAgB;IAQzB,OAAO,CAAC,WAAW,CAMjB;IAEF,OAAO,CAAC,gBAAgB,CAEtB;IAEF,OAAO,CAAC,kBAAkB,CAaxB;IAIF,OAAO,CAAC,eAAe,CAsDrB;IAEF,OAAO,CAAC,eAAe,CAqCrB;IAEO,MAAM;CAoChB"}
package/dist/editor.js CHANGED
@@ -1,326 +1,203 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- /**
3
- * Adaptive FAQ - Editor Component
4
- *
5
- * Review & tweak editor for AI-generated FAQ question decisions.
6
- * Displays a scannable list of Q&A cards with trigger, rationale,
7
- * and inline editing. Includes detection badges and hover-to-highlight.
8
- */
9
- import { ConditionStatusLine, DetectionBadge, DismissedSection, EditorBody, EditorCard, EditorHeader, EditorInput, EditorLayout, EditorTextarea, EmptyState, GroupHeader, TriggerJourney, useTriggerWhenStatus, } from '@syntrologie/shared-editor-ui';
10
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
11
- import { describeTrigger, summarizeFAQItem } from './summarize';
12
- import { isOwnAction, } from './types';
13
- function isRuleStrategy(s) {
14
- return (typeof s === 'object' &&
15
- s !== null &&
16
- s.type === 'rules' &&
17
- Array.isArray(s.rules));
1
+ // src/editor.ts
2
+ import { html, LitElement, nothing } from "lit";
3
+
4
+ // src/summarize.ts
5
+ var MAX_Q_LEN = 50;
6
+ var MAX_A_LEN = 40;
7
+ function truncate(text, max) {
8
+ if (text.length <= max) return text;
9
+ return `${text.slice(0, max).trimEnd()}...`;
18
10
  }
19
- function extractTargetingInfo(triggerWhen) {
20
- if (!triggerWhen || !isRuleStrategy(triggerWhen)) {
21
- return { pagePatterns: [], anchorSelectors: [], hasTargeting: false };
22
- }
23
- const pagePatterns = new Set();
24
- const anchorSelectors = new Set();
25
- for (const rule of triggerWhen.rules) {
26
- for (const cond of rule.conditions) {
27
- const c = cond;
28
- if (c.type === 'page_url' && typeof c.url === 'string') {
29
- pagePatterns.add(c.url);
30
- }
31
- else if (c.type === 'anchor_visible' && typeof c.anchorId === 'string') {
32
- anchorSelectors.add(c.anchorId);
33
- }
34
- }
35
- }
36
- const hasTargeting = pagePatterns.size > 0 || anchorSelectors.size > 0;
37
- return {
38
- pagePatterns: [...pagePatterns],
39
- anchorSelectors: [...anchorSelectors],
40
- hasTargeting,
41
- };
11
+ function stripHtml(html2) {
12
+ return html2.replace(/<[^>]*>/g, "").trim();
42
13
  }
43
- function extractFirstPage(triggerWhen) {
44
- const info = extractTargetingInfo(triggerWhen);
45
- return info.pagePatterns[0] || null;
14
+ function getAnswerPreview(answer) {
15
+ if (typeof answer === "string") return answer;
16
+ if (answer.type === "rich") return stripHtml(answer.html);
17
+ return answer.content.replace(/[*_#`]/g, "").trim();
46
18
  }
47
- function extractFirstAnchor(triggerWhen) {
48
- const info = extractTargetingInfo(triggerWhen);
49
- return info.anchorSelectors[0] || null;
19
+ function summarizeFAQItem(item) {
20
+ const q = truncate(item.config.question, MAX_Q_LEN);
21
+ const a = truncate(getAnswerPreview(item.config.answer), MAX_A_LEN);
22
+ return `Q: "${q}" \u2014 ${a}`;
50
23
  }
51
- /** Save a pending highlight selector to sessionStorage (inlined to avoid cross-package import). */
52
- function savePendingHighlight(selector) {
53
- try {
54
- sessionStorage.setItem('syntro:editor:pending-highlight', selector);
55
- }
56
- catch {
57
- // Silently ignore
58
- }
24
+
25
+ // src/types.ts
26
+ var ACTION_NAMESPACE = "faq";
27
+ function isOwnAction(action) {
28
+ return action.kind.startsWith(`${ACTION_NAMESPACE}:`);
59
29
  }
60
- // ============================================================================
61
- // Helpers
62
- // ============================================================================
63
- function getAnswerText(answer) {
64
- if (typeof answer === 'string')
65
- return answer;
66
- if (answer.type === 'rich')
67
- return answer.html;
68
- return answer.content;
69
- }
70
- export function flattenItems(config) {
71
- const actions = (config.actions || []).filter(isOwnAction);
72
- return actions.map((q, i) => ({
73
- key: String(i),
74
- index: i,
75
- summary: summarizeFAQItem(q),
76
- trigger: describeTrigger(q.triggerWhen),
77
- rationale: q.rationale,
78
- firstAnchor: extractFirstAnchor(q.triggerWhen),
79
- question: q,
80
- }));
81
- }
82
- function filterConfig(config, dismissedKeys) {
83
- const ownActions = (config.actions || []).filter(isOwnAction);
84
- return {
85
- ...config,
86
- actions: ownActions.filter((_, i) => !dismissedKeys.has(String(i))),
30
+
31
+ // src/editor.ts
32
+ var getAnswerText = (answer) => {
33
+ if (typeof answer === "string") return answer;
34
+ if (answer.type === "rich") return answer.html;
35
+ return answer.content;
36
+ };
37
+ var flattenItems = (config) => {
38
+ const actions = (config.actions || []).filter(isOwnAction);
39
+ return actions.map((q, i) => ({
40
+ key: String(i),
41
+ index: i,
42
+ summary: summarizeFAQItem(q),
43
+ question: q
44
+ }));
45
+ };
46
+ var FAQEditorLit = class extends LitElement {
47
+ constructor() {
48
+ super(...arguments);
49
+ this.config = null;
50
+ this.onChange = null;
51
+ this._editingKey = null;
52
+ // No auto-dirty on load — only dirty when user makes changes (matching React behavior).
53
+ // ---- Actions ----
54
+ this._handleBack = () => {
55
+ if (this._editingKey !== null) {
56
+ this._editingKey = null;
57
+ } else {
58
+ this.dispatchEvent(new CustomEvent("navigate-home", { bubbles: true }));
59
+ }
87
60
  };
88
- }
89
- function useDetection(items, getCurrentRoute) {
90
- const [detectionMap, setDetectionMap] = useState(new Map());
91
- const itemsRef = useRef(items);
92
- itemsRef.current = items;
93
- useEffect(() => {
94
- const runDetection = () => {
95
- const map = new Map();
96
- const currentPath = getCurrentRoute();
97
- for (const item of itemsRef.current) {
98
- const targeting = extractTargetingInfo(item.question.triggerWhen);
99
- // Check page match
100
- let pageMatch = true;
101
- if (targeting.pagePatterns.length > 0) {
102
- pageMatch = targeting.pagePatterns.some((pattern) => {
103
- const regex = new RegExp(`^${pattern.replace(/\*\*/g, '.*').replace(/(?<!\.)(\*)/g, '[^/]*')}$`);
104
- return regex.test(currentPath);
105
- });
106
- }
107
- // Check anchor presence
108
- let anchorFound = false;
109
- let element = null;
110
- if (item.firstAnchor) {
111
- try {
112
- element = document.querySelector(item.firstAnchor);
113
- anchorFound = element !== null;
114
- }
115
- catch {
116
- // Invalid selector
117
- }
118
- }
119
- else {
120
- // No anchor to check — if page matches, consider found
121
- anchorFound = pageMatch;
122
- }
123
- map.set(item.key, {
124
- found: pageMatch && anchorFound,
125
- element,
126
- });
127
- }
128
- setDetectionMap(map);
129
- };
130
- runDetection();
131
- const interval = setInterval(runDetection, 2000);
132
- window.addEventListener('popstate', runDetection);
133
- return () => {
134
- clearInterval(interval);
135
- window.removeEventListener('popstate', runDetection);
136
- };
137
- }, [getCurrentRoute]);
138
- return detectionMap;
139
- }
140
- // ============================================================================
141
- // FAQEditor Component
142
- // ============================================================================
143
- export function FAQEditor({ config, onChange, editor }) {
144
- const typedConfig = config;
145
- const [dismissedKeys, setDismissedKeys] = useState(() => editor.getDismissedKeys?.() ?? new Set());
146
- const [editingKey, setEditingKey] = useState(null);
147
- const [_previewMode, setPreviewMode] = useState('after');
148
- const [_hoveredKey, setHoveredKey] = useState(null);
149
- // Sync dismissed keys back to navigation context on every change
150
- useEffect(() => {
151
- editor.setDismissedKeys?.(dismissedKeys);
152
- }, [dismissedKeys, editor]);
153
- // React to global before/after toggle from the panel
154
- // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally omitted — adding config/typedConfig/previewConfig would cause infinite re-renders since previewConfig triggers state updates
155
- useEffect(() => {
156
- const mode = editor.previewMode;
157
- if (!mode)
158
- return;
159
- if (mode === 'before') {
160
- // Remove all FAQ items — push a config with every item filtered out
161
- const allKeys = new Set(flattenItems(typedConfig).map((item) => item.key));
162
- const empty = filterConfig(typedConfig, allKeys);
163
- editor.previewConfig(empty);
164
- }
165
- else {
166
- // Restore the full config
167
- editor.previewConfig(config);
168
- }
169
- }, [editor.previewMode]);
170
- // If navigated here with an editKey, jump directly to that item's edit view
171
- const initialConsumed = useRef(false);
172
- useEffect(() => {
173
- if (!initialConsumed.current) {
174
- initialConsumed.current = true;
175
- if (editor.initialEditKey != null) {
176
- setEditingKey(String(editor.initialEditKey));
177
- }
178
- editor.clearInitialState?.();
179
- }
180
- }, [editor]);
181
- const allItems = flattenItems(typedConfig);
182
- const activeItems = allItems.filter((item) => !dismissedKeys.has(item.key));
183
- const dismissedItems = allItems.filter((item) => dismissedKeys.has(item.key));
184
- const totalQuestions = activeItems.length;
185
- const detectionMap = useDetection(allItems, editor.getCurrentRoute);
186
- const foundCount = activeItems.filter((item) => detectionMap.get(item.key)?.found).length;
187
- // Live triggerWhen status for condition diagnostics
188
- const triggerWhenItems = useMemo(() => allItems.map((item) => ({
189
- id: item.key,
190
- triggerWhen: item.question.triggerWhen,
191
- })), [allItems]);
192
- const triggerWhenStatuses = useTriggerWhenStatus(triggerWhenItems);
193
- const handleDismiss = useCallback((key) => {
194
- setDismissedKeys((prev) => {
195
- const next = new Set(prev);
196
- next.add(key);
197
- return next;
198
- });
199
- if (editingKey === key)
200
- setEditingKey(null);
201
- }, [editingKey]);
202
- const handleRestore = useCallback((key) => {
203
- setDismissedKeys((prev) => {
204
- const next = new Set(prev);
205
- next.delete(key);
206
- return next;
207
- });
208
- }, []);
209
- const handleCardBodyClick = useCallback((item) => {
210
- setEditingKey(item.key);
211
- }, []);
212
- const handleTriggerClick = useCallback(async (item) => {
213
- const pageUrl = extractFirstPage(item.question.triggerWhen);
214
- if (pageUrl) {
215
- if (item.firstAnchor)
216
- savePendingHighlight(item.firstAnchor);
217
- await editor.navigateTo(pageUrl);
218
- }
219
- if (item.firstAnchor) {
220
- editor.highlightElement(item.firstAnchor);
221
- }
222
- }, [editor]);
223
- const handleBackToList = useCallback(() => {
224
- setEditingKey(null);
225
- setPreviewMode('after');
226
- editor.previewConfig(config);
227
- editor.clearHighlight();
228
- }, [editor, config]);
229
- // Register back handler in panel header when editing
230
- useEffect(() => {
231
- editor.setBackHandler?.(editingKey !== null ? handleBackToList : null);
232
- return () => editor.setBackHandler?.(null);
233
- }, [editingKey, handleBackToList, editor]);
234
- const handleFieldChange = useCallback((index, field, value) => {
235
- const ownActions = (typedConfig.actions || []).filter(isOwnAction).slice();
236
- const q = { ...ownActions[index], config: { ...ownActions[index].config } };
237
- q.config[field] = value;
238
- ownActions[index] = q;
239
- const otherActions = (typedConfig.actions || []).filter((a) => !isOwnAction(a));
240
- const updated = { ...typedConfig, actions: [...otherActions, ...ownActions] };
241
- onChange(updated);
242
- editor.setDirty(true);
243
- }, [typedConfig, onChange, editor]);
244
- const _handlePublish = useCallback(() => {
245
- if (dismissedKeys.size > 0) {
246
- const filtered = filterConfig(typedConfig, dismissedKeys);
247
- onChange(filtered);
248
- }
249
- editor.publish();
250
- }, [dismissedKeys, typedConfig, onChange, editor]);
251
- const handleBadgeClick = useCallback(async (item) => {
252
- const detection = detectionMap.get(item.key);
253
- if (detection?.found && item.firstAnchor) {
254
- editor.highlightElement(item.firstAnchor);
255
- }
256
- else {
257
- const pageUrl = extractFirstPage(item.question.triggerWhen);
258
- if (pageUrl) {
259
- if (item.firstAnchor)
260
- savePendingHighlight(item.firstAnchor);
261
- await editor.navigateTo(pageUrl);
262
- if (item.firstAnchor)
263
- editor.highlightElement(item.firstAnchor);
264
- }
265
- else if (item.firstAnchor) {
266
- editor.highlightElement(item.firstAnchor);
267
- }
268
- }
269
- }, [editor, detectionMap]);
270
- const handleCardHover = useCallback((item) => {
271
- setHoveredKey(item.key);
272
- if (item.firstAnchor) {
273
- editor.highlightElement(item.firstAnchor);
274
- }
275
- }, [editor]);
276
- const handleCardLeave = useCallback(() => {
277
- setHoveredKey(null);
278
- editor.clearHighlight();
279
- }, [editor]);
280
- // ---- Edit form renderer ----
281
- const renderEditFields = (index) => {
282
- const actions = (typedConfig.actions || []).filter(isOwnAction);
283
- const q = actions[index];
284
- if (!q)
285
- return null;
286
- const item = allItems.find((it) => it.key === String(index));
287
- return (_jsxs("div", { className: "se-py-1", children: [item && item.trigger !== 'All pages' && (_jsxs("button", { type: "button", "data-trigger": true, className: "se-flex se-items-center se-gap-1 se-text-[11px] se-text-slate-grey-8 se-cursor-pointer se-mb-1 se-border-none se-bg-transparent se-p-0 se-text-left", onClick: () => handleTriggerClick(item), children: [_jsx("span", { children: '\u{1f4cd}' }), _jsx("span", { children: item.trigger })] })), _jsx(EditorInput, { label: "Question", value: q.config.question, onChange: (e) => handleFieldChange(index, 'question', e.target.value) }), _jsx(EditorTextarea, { label: "Answer", value: getAnswerText(q.config.answer), onChange: (e) => handleFieldChange(index, 'answer', e.target.value) }), _jsx(EditorInput, { label: "Category", value: q.config.category || '', onChange: (e) => handleFieldChange(index, 'category', e.target.value || undefined), placeholder: "e.g., Billing, Account" }), _jsxs("div", { children: [_jsx("span", { className: "se-text-[11px] se-font-semibold se-text-slate-grey-7 se-mb-1 se-block", children: "AI Rationale" }), _jsx("div", { className: "se-p-2 se-rounded se-border se-border-dashed se-border-white/15 se-bg-white/[0.02] se-text-slate-grey-8 se-text-xs se-mb-2", children: q.rationale ? q.rationale.why : 'N/A' })] }), _jsx(TriggerJourney, { status: triggerWhenStatuses.get(String(index)) ?? null })] }));
61
+ this._handleItemClick = (key) => {
62
+ this._editingKey = key;
288
63
  };
289
- return (_jsxs(EditorLayout, { children: [_jsx(EditorHeader, { title: "Review Questions", subtitle: `${totalQuestions} question${totalQuestions !== 1 ? 's' : ''}${totalQuestions > 0 ? ` (${foundCount} found on this page)` : ''}`, onBack: () => editor.navigateHome() }), _jsx(EditorBody, { children: editingKey !== null ? (
290
- /* ---- Edit mode ---- */
291
- (() => {
292
- const editIndex = Number(editingKey);
293
- const editItem = allItems.find((it) => it.key === editingKey);
294
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "se-flex se-items-center se-gap-2 se-mb-3 se-text-[13px] se-font-semibold se-text-slate-grey-10", children: [_jsx("span", { children: '\u2753' }), _jsx("span", { children: editItem?.summary })] }), renderEditFields(editIndex)] }));
295
- })()) : (
296
- /* ---- List mode ---- */
297
- _jsxs(_Fragment, { children: [allItems.length === 0 && _jsx(EmptyState, { message: "No FAQ questions configured." }), activeItems.length > 0 && (_jsxs(_Fragment, { children: [_jsx(GroupHeader, { label: "FAQ", count: activeItems.length }), activeItems.map((item) => {
298
- const detection = detectionMap.get(item.key);
299
- return (_jsxs(EditorCard, { itemKey: item.key, onClick: () => handleCardBodyClick(item), onMouseEnter: () => handleCardHover(item), onMouseLeave: handleCardLeave, children: [item.trigger !== 'All pages' && (_jsxs("button", { type: "button", "data-trigger": true, className: "se-flex se-items-center se-gap-1 se-text-[11px] se-text-slate-grey-8 se-cursor-pointer se-mb-1 se-border-none se-bg-transparent se-p-0 se-text-left", onClick: (e) => {
300
- e.stopPropagation();
301
- handleTriggerClick(item);
302
- }, children: [_jsx("span", { children: '\u{1f4cd}' }), _jsx("span", { children: item.trigger })] })), _jsxs("div", { "data-card-body": true, role: "button", tabIndex: 0, className: "se-flex se-items-center se-gap-2 se-cursor-pointer", onClick: () => handleCardBodyClick(item), onKeyDown: (e) => {
303
- if (e.key === 'Enter' || e.key === ' ')
304
- handleCardBodyClick(item);
305
- }, children: [_jsx(DetectionBadge, { found: detection?.found ?? false, onClick: () => handleBadgeClick(item) }), _jsx("span", { className: "se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap", children: item.summary }), _jsx("button", { type: "button", className: "se-py-0.5 se-px-1.5 se-rounded se-border-none se-bg-transparent se-text-slate-grey-7 se-text-sm se-cursor-pointer se-shrink-0 se-leading-none", onClick: (e) => {
306
- e.stopPropagation();
307
- handleDismiss(item.key);
308
- }, title: "Dismiss this question", children: "\u00D7" })] }), _jsxs("div", { className: "se-text-[10px] se-text-slate-grey-7 se-mt-1", children: ["WHY: ", item.rationale ? item.rationale.why : 'N/A'] }), _jsx(ConditionStatusLine, { status: triggerWhenStatuses.get(item.key) ?? null })] }, item.key));
309
- })] })), dismissedItems.length > 0 && (_jsx(DismissedSection, { count: dismissedItems.length, children: dismissedItems.map((item) => (_jsxs("div", { className: "se-flex se-items-center se-gap-2 se-py-1.5 se-px-2.5 se-rounded-md se-border se-border-white/[0.03] se-bg-transparent se-mb-0.5 se-cursor-pointer se-text-xs se-text-slate-grey-6 se-opacity-60", children: [_jsx("span", { className: "se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap se-line-through", children: item.summary }), _jsx("button", { type: "button", className: "se-py-0.5 se-px-1.5 se-rounded se-border-none se-bg-transparent se-text-blue-5 se-text-[11px] se-cursor-pointer se-shrink-0 se-leading-none", onClick: (e) => {
310
- e.stopPropagation();
311
- handleRestore(item.key);
312
- }, children: "Restore" })] }, item.key))) }))] })) })] }));
313
- }
314
- /**
315
- * Editor panel configuration for the app registry.
316
- */
317
- export const editorPanel = {
318
- title: 'FAQ',
319
- icon: '\u2753',
320
- description: 'FAQ accordion with per-item visibility',
64
+ this._handleFieldChange = (index, field, value) => {
65
+ if (!this.config || !this.onChange) return;
66
+ const ownActions = (this.config.actions || []).filter(isOwnAction).slice();
67
+ const q = { ...ownActions[index], config: { ...ownActions[index].config } };
68
+ q.config[field] = value;
69
+ ownActions[index] = q;
70
+ const otherActions = (this.config.actions || []).filter((a) => !isOwnAction(a));
71
+ const updated = { ...this.config, actions: [...otherActions, ...ownActions] };
72
+ this.onChange(updated);
73
+ this.dispatchEvent(new CustomEvent("dirty-change", { detail: { dirty: true }, bubbles: true }));
74
+ };
75
+ // ---- Render ----
76
+ this._renderEditMode = (item) => {
77
+ const q = item.question;
78
+ return html`
79
+ <div class="se-py-1">
80
+ <div class="se-flex se-items-center se-gap-2 se-mb-3 se-text-[13px] se-font-semibold se-text-text-primary">
81
+ <span>❓</span>
82
+ <span>${item.summary}</span>
83
+ </div>
84
+
85
+ <div class="se-mb-3">
86
+ <label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Question</label>
87
+ <input
88
+ type="text"
89
+ .value=${q.config.question}
90
+ @input=${(e) => this._handleFieldChange(item.index, "question", e.target.value)}
91
+ class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm"
92
+ />
93
+ </div>
94
+
95
+ <div class="se-mb-3">
96
+ <label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Answer</label>
97
+ <textarea
98
+ .value=${getAnswerText(q.config.answer)}
99
+ @input=${(e) => this._handleFieldChange(item.index, "answer", e.target.value)}
100
+ rows="4"
101
+ class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm se-resize-none"
102
+ ></textarea>
103
+ </div>
104
+
105
+ <div class="se-mb-3">
106
+ <label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Category</label>
107
+ <input
108
+ type="text"
109
+ .value=${q.config.category || ""}
110
+ placeholder="e.g., Billing, Account"
111
+ @input=${(e) => this._handleFieldChange(item.index, "category", e.target.value)}
112
+ class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm"
113
+ />
114
+ </div>
115
+
116
+ ${q.rationale ? html`
117
+ <div>
118
+ <span class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">AI Rationale</span>
119
+ <div class="se-p-2 se-rounded se-border se-border-dashed se-border-border-primary se-bg-card-bg se-text-text-secondary se-text-xs se-mb-2">
120
+ ${q.rationale.why}
121
+ </div>
122
+ </div>
123
+ ` : nothing}
124
+ </div>
125
+ `;
126
+ };
127
+ this._renderListMode = (items) => {
128
+ if (items.length === 0) {
129
+ return html`<div class="se-text-center se-py-8 se-px-4 se-text-text-secondary se-text-sm">
130
+ No FAQ questions configured.
131
+ </div>`;
132
+ }
133
+ return html`
134
+ <div class="se-text-[11px] se-font-semibold se-uppercase se-tracking-wider se-text-text-secondary se-mb-2 se-px-1">
135
+ FAQ <span class="se-text-text-tertiary se-font-normal">${items.length}</span>
136
+ </div>
137
+ ${items.map(
138
+ (item) => html`
139
+ <div
140
+ data-item-key=${item.key}
141
+ data-card-body
142
+ @click=${() => this._handleItemClick(item.key)}
143
+ class="se-p-3 se-rounded-lg se-border se-border-border-primary se-bg-card-bg se-cursor-pointer se-mb-2 se-transition-all hover:se-border-pink-4/40 hover:se-bg-pink-4/5"
144
+ >
145
+ <div class="se-flex se-items-center se-gap-2">
146
+ <span class="se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap se-text-sm se-text-text-primary">
147
+ ${item.summary}
148
+ </span>
149
+ </div>
150
+ ${item.question.rationale ? html`
151
+ <div class="se-text-[10px] se-text-text-tertiary se-mt-1">
152
+ WHY: ${item.question.rationale.why}
153
+ </div>
154
+ ` : nothing}
155
+ </div>
156
+ `
157
+ )}
158
+ `;
159
+ };
160
+ }
161
+ createRenderRoot() {
162
+ return this;
163
+ }
164
+ render() {
165
+ if (!this.config) {
166
+ return html`<div class="se-p-8 se-text-center se-text-text-secondary se-text-sm">Loading...</div>`;
167
+ }
168
+ const items = flattenItems(this.config);
169
+ const editItem = this._editingKey !== null ? items.find((it) => it.key === this._editingKey) : null;
170
+ return html`
171
+ <div class="se-flex se-flex-col se-h-full">
172
+ <!-- Header (no Back button — panel provides that) -->
173
+ <div class="se-px-4 se-pt-3 se-pb-1">
174
+ <h2 class="se-m-0 se-text-base se-font-semibold se-text-text-primary">Review Questions</h2>
175
+ <p class="se-m-0 se-mt-0.5 se-text-xs se-text-text-secondary">
176
+ ${items.length} question${items.length !== 1 ? "s" : ""}
177
+ </p>
178
+ </div>
179
+
180
+ <div class="se-flex-1 se-overflow-auto se-p-4">
181
+ ${editItem ? html`
182
+ <button type="button" @click=${() => {
183
+ this._editingKey = null;
184
+ }}
185
+ class="se-mb-3 se-py-1 se-px-2 se-rounded se-border-none se-bg-card-bg se-text-text-secondary se-text-xs se-cursor-pointer"
186
+ >← Back to list</button>
187
+ ${this._renderEditMode(editItem)}
188
+ ` : this._renderListMode(items)}
189
+ </div>
190
+ </div>
191
+ `;
192
+ }
193
+ };
194
+ FAQEditorLit.properties = {
195
+ config: { attribute: false },
196
+ onChange: { attribute: false },
197
+ _editingKey: { state: true }
321
198
  };
322
- export const editor = {
323
- panel: editorPanel,
324
- component: FAQEditor,
199
+ customElements.define("se-faq-editor", FAQEditorLit);
200
+ export {
201
+ FAQEditorLit
325
202
  };
326
- export default FAQEditor;
203
+ //# sourceMappingURL=editor.js.map