@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.
@@ -1,534 +0,0 @@
1
- /**
2
- * Adaptive FAQ - FAQWidgetLit
3
- *
4
- * Lit web component equivalent of FAQWidget.tsx.
5
- * Renders a collapsible Q&A accordion with search, category grouping,
6
- * feedback, and markdown rendering — all as a custom element with no
7
- * Shadow DOM (light DOM via createRenderRoot).
8
- *
9
- * Tag name: <syntro-faq-accordion>
10
- *
11
- * Decorator-free: uses `static override properties` (tsconfig has no
12
- * experimentalDecorators).
13
- */
14
- import { purple } from '@syntro/design-system/tokens';
15
- import { html, LitElement, nothing } from 'lit';
16
- import { styleMap } from 'lit/directives/style-map.js';
17
- import { unsafeHTML } from 'lit/directives/unsafe-html.js';
18
- import { Marked } from 'marked';
19
- import { baseStyles, themeStyles } from './faq-styles';
20
- // ============================================================================
21
- // Utility — styleMap accepts Record<string, string | number | undefined>
22
- // but its TypeScript signature is overly narrow in some Lit versions.
23
- // Cast through unknown to avoid false positives when style values include
24
- // numeric CSS properties (fontWeight, lineHeight, etc.).
25
- // ============================================================================
26
- function sm(styles) {
27
- return styles;
28
- }
29
- const marked = new Marked({ async: false, gfm: true, breaks: true });
30
- // ============================================================================
31
- // Helpers (mirrored from FAQWidget.tsx)
32
- // ============================================================================
33
- function getAnswerText(answer) {
34
- if (typeof answer === 'string')
35
- return answer;
36
- if (answer.type === 'rich')
37
- return answer.html;
38
- return answer.content;
39
- }
40
- function renderAnswerHtml(answer) {
41
- if (typeof answer === 'string') {
42
- return marked.parse(answer);
43
- }
44
- if (answer.type === 'rich') {
45
- return answer.html;
46
- }
47
- return marked.parse(answer.content);
48
- }
49
- function resolveFeedbackConfig(feedback) {
50
- if (!feedback)
51
- return null;
52
- if (feedback === true)
53
- return { style: 'thumbs' };
54
- return feedback;
55
- }
56
- function getFeedbackPrompt(feedbackConfig) {
57
- return feedbackConfig.prompt ?? 'Was this helpful?';
58
- }
59
- function resolveTheme(theme) {
60
- if (theme && theme !== 'auto')
61
- return theme;
62
- if (typeof window !== 'undefined') {
63
- return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
64
- }
65
- return 'light';
66
- }
67
- // ============================================================================
68
- // FAQAccordionElement — Lit element
69
- // ============================================================================
70
- /**
71
- * <syntro-faq-accordion> — light-DOM Lit web component.
72
- *
73
- * Set properties imperatively (no attribute serialisation for objects):
74
- * el.faqConfig = { expandBehavior: 'single', ... };
75
- * el.runtime = runtimeInstance;
76
- * el.instanceId = 'my-faq';
77
- */
78
- export class FAQAccordionElement extends LitElement {
79
- constructor() {
80
- // -----------------------------------------------------------------------
81
- // Reactive properties (no decorators — tsconfig forbids experimentalDecorators)
82
- // -----------------------------------------------------------------------
83
- super(...arguments);
84
- // -----------------------------------------------------------------------
85
- // Property declarations
86
- // -----------------------------------------------------------------------
87
- this.faqConfig = {
88
- expandBehavior: 'single',
89
- searchable: false,
90
- theme: 'auto',
91
- actions: [],
92
- };
93
- this.runtime = null;
94
- this.instanceId = 'faq-widget';
95
- // Internal state
96
- this._expandedIds = new Set();
97
- this._highlightId = null;
98
- this._searchQuery = '';
99
- this._feedbackState = new Map();
100
- this._hoveredId = null;
101
- // Subscription cleanup handles
102
- this._unsubContext = null;
103
- this._unsubAccumulator = null;
104
- this._unsubCta = null;
105
- this._unsubDeepLink = null;
106
- this._unsubSessionMetrics = null;
107
- this._highlightTimer = null;
108
- }
109
- // -----------------------------------------------------------------------
110
- // Light DOM — no Shadow DOM so CSS variables from the host page apply
111
- // -----------------------------------------------------------------------
112
- createRenderRoot() {
113
- return this;
114
- }
115
- // -----------------------------------------------------------------------
116
- // Lifecycle
117
- // -----------------------------------------------------------------------
118
- connectedCallback() {
119
- super.connectedCallback();
120
- this._subscribeAll();
121
- }
122
- disconnectedCallback() {
123
- super.disconnectedCallback();
124
- this._unsubscribeAll();
125
- if (this._highlightTimer !== null) {
126
- clearTimeout(this._highlightTimer);
127
- this._highlightTimer = null;
128
- }
129
- }
130
- // Re-subscribe when runtime changes (property may be set after connectedCallback)
131
- updated(changedProps) {
132
- if (changedProps.has('runtime')) {
133
- this._unsubscribeAll();
134
- this._subscribeAll();
135
- }
136
- }
137
- // -----------------------------------------------------------------------
138
- // Subscription management
139
- // -----------------------------------------------------------------------
140
- _subscribeAll() {
141
- if (!this.runtime)
142
- return;
143
- // Context changes → force re-render
144
- this._unsubContext = this.runtime.context.subscribe(() => {
145
- this.requestUpdate();
146
- });
147
- // Accumulator changes → force re-render (for event_count triggerWhen)
148
- if (this.runtime.accumulator?.subscribe) {
149
- this._unsubAccumulator = this.runtime.accumulator.subscribe(() => {
150
- this.requestUpdate();
151
- });
152
- }
153
- // Session metric changes → force re-render (for session_metric triggerWhen)
154
- if (this.runtime.sessionMetrics?.subscribe) {
155
- this._unsubSessionMetrics = this.runtime.sessionMetrics.subscribe(() => {
156
- this.requestUpdate();
157
- });
158
- }
159
- // faq:open:* events from overlay CTA clicks
160
- if (this.runtime.events.subscribe) {
161
- // Check EventBus history for pending faq:open events
162
- if (this.runtime.events.getRecent) {
163
- const recentEvents = this.runtime.events.getRecent({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, 10);
164
- const pendingEvent = recentEvents
165
- .filter((e) => {
166
- const actionId = e.props?.actionId;
167
- return typeof actionId === 'string' && actionId.startsWith('faq:open:');
168
- })
169
- .pop();
170
- if (pendingEvent && Date.now() - pendingEvent.ts < 10000) {
171
- const questionId = pendingEvent.props.actionId.replace('faq:open:', '');
172
- this._expandedIds = new Set([questionId]);
173
- requestAnimationFrame(() => {
174
- const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
175
- if (el)
176
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
177
- });
178
- }
179
- }
180
- this._unsubCta = this.runtime.events.subscribe({ patterns: ['^action\\.tooltip_cta_clicked$', '^action\\.modal_cta_clicked$'] }, (event) => {
181
- const actionId = event.props?.actionId;
182
- if (typeof actionId !== 'string' || !actionId.startsWith('faq:open:'))
183
- return;
184
- const questionId = actionId.replace('faq:open:', '');
185
- this._expandedIds = new Set([questionId]);
186
- requestAnimationFrame(() => {
187
- const el = document.querySelector(`[data-faq-item-id="${questionId}"]`);
188
- if (el)
189
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
190
- });
191
- this.runtime?.events.publish('canvas.requestOpen');
192
- });
193
- }
194
- // notification.deep_link events
195
- if (this.runtime.events.subscribe) {
196
- const handleDeepLink = (event) => {
197
- const tileId = event.props?.tileId;
198
- const itemId = event.props?.itemId;
199
- if (tileId !== this.instanceId)
200
- return;
201
- if (!itemId)
202
- return;
203
- this._expandedIds = new Set([itemId]);
204
- this._highlightId = itemId;
205
- if (this._highlightTimer !== null)
206
- clearTimeout(this._highlightTimer);
207
- this._highlightTimer = setTimeout(() => {
208
- this._highlightId = null;
209
- this._highlightTimer = null;
210
- }, 1500);
211
- requestAnimationFrame(() => {
212
- const el = document.querySelector(`[data-faq-item-id="${itemId}"]`);
213
- if (el)
214
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
215
- });
216
- };
217
- // Check recent events (may have fired before widget mounted)
218
- if (this.runtime.events.getRecent) {
219
- const recent = this.runtime.events.getRecent({ names: ['notification.deep_link'] }, 5);
220
- const pending = recent
221
- .filter((e) => e.props?.tileId === this.instanceId && e.props?.itemId)
222
- .pop();
223
- if (pending && Date.now() - pending.ts < 10000) {
224
- handleDeepLink(pending);
225
- }
226
- }
227
- this._unsubDeepLink = this.runtime.events.subscribe({ names: ['notification.deep_link'] }, handleDeepLink);
228
- }
229
- }
230
- _unsubscribeAll() {
231
- this._unsubContext?.();
232
- this._unsubAccumulator?.();
233
- this._unsubSessionMetrics?.();
234
- this._unsubCta?.();
235
- this._unsubDeepLink?.();
236
- this._unsubContext = null;
237
- this._unsubAccumulator = null;
238
- this._unsubSessionMetrics = null;
239
- this._unsubCta = null;
240
- this._unsubDeepLink = null;
241
- }
242
- // -----------------------------------------------------------------------
243
- // Handlers
244
- // -----------------------------------------------------------------------
245
- _handleToggle(id) {
246
- const prev = this._expandedIds;
247
- let next;
248
- if (this.faqConfig.expandBehavior === 'single') {
249
- next = prev.has(id) ? new Set() : new Set([id]);
250
- }
251
- else {
252
- next = new Set(prev);
253
- if (prev.has(id)) {
254
- next.delete(id);
255
- }
256
- else {
257
- next.add(id);
258
- }
259
- }
260
- const willBeExpanded = !prev.has(id);
261
- this._expandedIds = next;
262
- this.runtime?.events.publish('faq:toggled', {
263
- instanceId: this.instanceId,
264
- questionId: id,
265
- expanded: willBeExpanded,
266
- timestamp: Date.now(),
267
- });
268
- }
269
- _handleFeedback(itemId, question, value) {
270
- const next = new Map(this._feedbackState);
271
- next.set(itemId, value);
272
- this._feedbackState = next;
273
- this.runtime?.events.publish('faq:feedback', { itemId, question, value });
274
- }
275
- // -----------------------------------------------------------------------
276
- // Computed helpers
277
- // -----------------------------------------------------------------------
278
- _visibleQuestions() {
279
- return (this.faqConfig.actions ?? []).filter((q) => {
280
- if (!q.triggerWhen)
281
- return true;
282
- if (!this.runtime)
283
- return true;
284
- const result = this.runtime.evaluateSync(q.triggerWhen);
285
- return result.value;
286
- });
287
- }
288
- _orderedQuestions(visible) {
289
- if (this.faqConfig.ordering === 'priority') {
290
- return [...visible].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
291
- }
292
- return visible;
293
- }
294
- _filteredQuestions(ordered) {
295
- const q = this._searchQuery.trim().toLowerCase();
296
- if (!this.faqConfig.searchable || !q)
297
- return ordered;
298
- return ordered.filter((item) => item.config.question.toLowerCase().includes(q) ||
299
- getAnswerText(item.config.answer).toLowerCase().includes(q) ||
300
- item.config.category?.toLowerCase().includes(q));
301
- }
302
- _categoryGroups(filtered) {
303
- const groups = new Map();
304
- for (const item of filtered) {
305
- const cat = item.config.category;
306
- if (!groups.has(cat))
307
- groups.set(cat, []);
308
- groups.get(cat).push(item);
309
- }
310
- return groups;
311
- }
312
- // -----------------------------------------------------------------------
313
- // Render helpers
314
- // -----------------------------------------------------------------------
315
- _renderAnswer(answer) {
316
- const html_str = renderAnswerHtml(answer);
317
- return html `<div style="margin:0" data-faq-markdown="">${unsafeHTML(html_str)}</div>`;
318
- }
319
- _renderFeedback(item, feedbackConfig, feedbackValue, theme) {
320
- const colors = themeStyles[theme];
321
- const feedbackStyle = { ...baseStyles.feedback, ...colors.feedbackPrompt };
322
- return html `
323
- <div style=${styleMap(sm(feedbackStyle))}>
324
- <span>${getFeedbackPrompt(feedbackConfig)}</span>
325
- <button
326
- type="button"
327
- style=${styleMap(sm({
328
- ...baseStyles.feedbackButton,
329
- ...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),
330
- }))}
331
- aria-label="Thumbs up"
332
- @click=${() => this._handleFeedback(item.config.id, item.config.question, 'up')}
333
- >\uD83D\uDC4D</button>
334
- <button
335
- type="button"
336
- style=${styleMap(sm({
337
- ...baseStyles.feedbackButton,
338
- ...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),
339
- }))}
340
- aria-label="Thumbs down"
341
- @click=${() => this._handleFeedback(item.config.id, item.config.question, 'down')}
342
- >\uD83D\uDC4E</button>
343
- </div>
344
- `;
345
- }
346
- _renderItem(item, isLast, theme, feedbackConfig) {
347
- const colors = themeStyles[theme];
348
- const isExpanded = this._expandedIds.has(item.config.id);
349
- const isHighlighted = this._highlightId === item.config.id;
350
- const isHovered = this._hoveredId === item.config.id;
351
- const itemStyle = {
352
- ...baseStyles.item,
353
- ...colors.item,
354
- ...(isExpanded ? colors.itemExpanded : {}),
355
- ...(isHighlighted
356
- ? {
357
- boxShadow: `0 0 0 2px ${purple[4]}, 0 0 12px rgba(106, 89, 206, 0.4)`,
358
- transition: 'box-shadow 0.3s ease',
359
- }
360
- : {}),
361
- ...(!isLast ? { borderBottom: 'var(--sc-content-item-divider, none)' } : {}),
362
- };
363
- const questionStyle = {
364
- ...baseStyles.question,
365
- ...colors.question,
366
- ...(isHovered ? colors.questionHover : {}),
367
- };
368
- const chevronStyle = {
369
- ...baseStyles.chevron,
370
- transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
371
- };
372
- const answerStyle = {
373
- ...baseStyles.answer,
374
- ...colors.answer,
375
- maxHeight: isExpanded ? '500px' : '0',
376
- paddingBottom: isExpanded ? '16px' : '0',
377
- };
378
- return html `
379
- <div
380
- style=${styleMap(sm(itemStyle))}
381
- data-faq-item-id=${item.config.id}
382
- >
383
- <button
384
- type="button"
385
- style=${styleMap(sm(questionStyle))}
386
- aria-expanded=${isExpanded}
387
- @click=${() => this._handleToggle(item.config.id)}
388
- @mouseenter=${() => {
389
- this._hoveredId = item.config.id;
390
- }}
391
- @mouseleave=${() => {
392
- this._hoveredId = null;
393
- }}
394
- >
395
- <span>${item.config.question}</span>
396
- <span style=${styleMap(sm(chevronStyle))}>\u203A</span>
397
- </button>
398
-
399
- <div
400
- style=${styleMap(sm(answerStyle))}
401
- aria-hidden=${!isExpanded}
402
- >
403
- ${this._renderAnswer(item.config.answer)}
404
- ${isExpanded && feedbackConfig
405
- ? this._renderFeedback(item, feedbackConfig, this._feedbackState.get(item.config.id), theme)
406
- : nothing}
407
- </div>
408
- </div>
409
- `;
410
- }
411
- _renderItems(items, theme, feedbackConfig) {
412
- return items.map((item, index) => this._renderItem(item, index === items.length - 1, theme, feedbackConfig));
413
- }
414
- // -----------------------------------------------------------------------
415
- // Render
416
- // -----------------------------------------------------------------------
417
- render() {
418
- const theme = resolveTheme(this.faqConfig.theme);
419
- const colors = themeStyles[theme];
420
- const feedbackConfig = resolveFeedbackConfig(this.faqConfig.feedback);
421
- const visible = this._visibleQuestions();
422
- const ordered = this._orderedQuestions(visible);
423
- const filtered = this._filteredQuestions(ordered);
424
- const hasCategories = filtered.some((q) => q.config.category);
425
- const groups = hasCategories ? this._categoryGroups(filtered) : null;
426
- const containerStyle = {
427
- ...baseStyles.container,
428
- ...colors.container,
429
- };
430
- const emptyStateStyle = {
431
- ...baseStyles.emptyState,
432
- ...colors.emptyState,
433
- };
434
- const categoryHeaderStyle = {
435
- ...baseStyles.categoryHeader,
436
- ...colors.categoryHeader,
437
- };
438
- const searchInputStyle = {
439
- ...baseStyles.searchInput,
440
- ...colors.searchInput,
441
- };
442
- // Empty state — no visible questions at all
443
- if (visible.length === 0) {
444
- return html `
445
- <div
446
- style=${styleMap(sm(containerStyle))}
447
- data-adaptive-id=${this.instanceId}
448
- data-adaptive-type="adaptive-faq"
449
- >
450
- <div style=${styleMap(sm(emptyStateStyle))}>
451
- You're all set for now! We'll surface answers here when they're relevant to what
452
- you're doing.
453
- </div>
454
- </div>
455
- `;
456
- }
457
- return html `
458
- <div
459
- style=${styleMap(sm(containerStyle))}
460
- data-adaptive-id=${this.instanceId}
461
- data-adaptive-type="adaptive-faq"
462
- >
463
- ${this.faqConfig.searchable
464
- ? html `
465
- <div style=${styleMap(sm(baseStyles.searchWrapper))}>
466
- <style>
467
- [data-adaptive-id="${this.instanceId}"] input::placeholder {
468
- color: var(--sc-content-search-color, inherit);
469
- opacity: 0.7;
470
- }
471
- </style>
472
- <input
473
- type="text"
474
- placeholder="Search questions..."
475
- .value=${this._searchQuery}
476
- style=${styleMap(sm(searchInputStyle))}
477
- @input=${(e) => {
478
- this._searchQuery = e.target.value;
479
- }}
480
- />
481
- </div>
482
- `
483
- : nothing}
484
-
485
- <div style=${styleMap(sm(baseStyles.accordion))}>
486
- ${groups
487
- ? Array.from(groups.entries()).map(([category, items]) => html `
488
- ${category
489
- ? html `
490
- <div
491
- style=${styleMap(sm(categoryHeaderStyle))}
492
- data-category-header=${category}
493
- >
494
- ${category}
495
- </div>
496
- `
497
- : nothing}
498
- ${this._renderItems(items, theme, feedbackConfig)}
499
- `)
500
- : this._renderItems(filtered, theme, feedbackConfig)}
501
- </div>
502
-
503
- ${this.faqConfig.searchable && filtered.length === 0 && this._searchQuery
504
- ? html `
505
- <div
506
- style=${styleMap(sm({ ...baseStyles.noResults, ...colors.emptyState }))}
507
- >
508
- No questions found matching &quot;${this._searchQuery}&quot;
509
- </div>
510
- `
511
- : nothing}
512
- </div>
513
- `;
514
- }
515
- }
516
- FAQAccordionElement.properties = {
517
- // Public API — set from the outside
518
- faqConfig: { attribute: false },
519
- runtime: { attribute: false },
520
- instanceId: { type: String },
521
- // Internal reactive state (prefixed with _ to signal "private")
522
- _expandedIds: { state: true },
523
- _highlightId: { state: true },
524
- _searchQuery: { state: true },
525
- _feedbackState: { state: true },
526
- _hoveredId: { state: true },
527
- };
528
- // ============================================================================
529
- // Custom element registration
530
- // ============================================================================
531
- if (!customElements.get('syntro-faq-accordion')) {
532
- customElements.define('syntro-faq-accordion', FAQAccordionElement);
533
- }
534
- export default FAQAccordionElement;
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) | undefined;
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;;;;;;;;;;;;;;;;;;;;2BAoCg4iB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAtB15iB,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;
@@ -1,37 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Lit Editor Component
3
- *
4
- * Lit web component port of the React FAQ editor (editor.tsx).
5
- * Displays FAQ question cards with inline editing, detection,
6
- * dismiss/restore, and rationale display.
7
- *
8
- * Custom events:
9
- * navigate-home — user clicked back
10
- * dirty-change — { dirty: boolean }
11
- */
12
- import { LitElement } from 'lit';
13
- import { type FAQConfig } from './types';
14
- export declare class FAQEditorLit extends LitElement {
15
- static properties: {
16
- config: {
17
- attribute: boolean;
18
- };
19
- onChange: {
20
- attribute: boolean;
21
- };
22
- _editingKey: {
23
- state: boolean;
24
- };
25
- };
26
- config: FAQConfig | null;
27
- onChange: ((updated: Record<string, unknown>) => void) | null;
28
- private _editingKey;
29
- createRenderRoot(): this;
30
- private _handleBack;
31
- private _handleItemClick;
32
- private _handleFieldChange;
33
- private _renderEditMode;
34
- private _renderListMode;
35
- render(): import("lit-html").TemplateResult<1>;
36
- }
37
- //# sourceMappingURL=editor-lit.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"editor-lit.d.ts","sourceRoot":"","sources":["../src/editor-lit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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,CAoCrB;IAEO,MAAM;CAoChB"}