@springbrand/message-panel 0.1.1 → 0.1.2

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 (99) hide show
  1. package/dist/demo/camel-chat-showcase.d.ts +1 -0
  2. package/dist/demo/camel-chat-showcase.js +1007 -0
  3. package/dist/demo/fixtures.d.ts +32 -0
  4. package/dist/demo/fixtures.js +250 -0
  5. package/dist/demo/index.js +4 -0
  6. package/dist/demo/message-panel-chat-showcase.d.ts +1 -0
  7. package/dist/demo/message-panel-chat-showcase.js +114 -0
  8. package/dist/demo/message-panel-gallery.d.ts +1 -0
  9. package/dist/demo/message-panel-gallery.js +130 -0
  10. package/dist/src/adapters/ai-sdk.d.ts +4 -0
  11. package/dist/src/adapters/ai-sdk.js +25 -0
  12. package/dist/src/camel/camel-chat-messages.d.ts +44 -0
  13. package/dist/src/camel/camel-chat-messages.js +421 -0
  14. package/dist/src/camel/camel-model-select.d.ts +13 -0
  15. package/dist/src/camel/camel-model-select.js +7 -0
  16. package/dist/src/camel/camel-plan-bar.d.ts +5 -0
  17. package/dist/src/camel/camel-plan-bar.js +24 -0
  18. package/dist/src/camel/camel-prompt-input.d.ts +32 -0
  19. package/dist/src/camel/camel-prompt-input.js +182 -0
  20. package/dist/src/camel/camel-tool-presentation.d.ts +21 -0
  21. package/dist/src/camel/camel-tool-presentation.js +784 -0
  22. package/dist/src/camel/camel-turn.d.ts +57 -0
  23. package/dist/src/camel/camel-turn.js +203 -0
  24. package/dist/src/camel/index.js +7 -0
  25. package/dist/src/camel/turn-summary-bar.d.ts +8 -0
  26. package/dist/src/camel/turn-summary-bar.js +12 -0
  27. package/dist/src/chat-summary-panel.d.ts +22 -0
  28. package/dist/src/chat-summary-panel.js +26 -0
  29. package/dist/src/composer/chat-composer.d.ts +4 -0
  30. package/dist/src/composer/chat-composer.js +90 -0
  31. package/dist/src/composer/composer-attachments.d.ts +13 -0
  32. package/dist/src/composer/composer-attachments.js +47 -0
  33. package/dist/src/composer/composer-trigger-popover.d.ts +31 -0
  34. package/dist/src/composer/composer-trigger-popover.js +29 -0
  35. package/dist/src/composer/composer.d.ts +15 -0
  36. package/dist/src/composer/composer.js +180 -0
  37. package/dist/src/composer/drop-controller.d.ts +8 -0
  38. package/dist/src/composer/drop-controller.js +69 -0
  39. package/dist/src/composer/index.js +6 -0
  40. package/dist/src/composer/types.d.ts +62 -0
  41. package/dist/src/composer/types.js +1 -0
  42. package/dist/src/contracts.d.ts +94 -0
  43. package/dist/src/contracts.js +1 -0
  44. package/dist/src/conversation.d.ts +13 -0
  45. package/dist/src/conversation.js +18 -0
  46. package/dist/src/index.js +12 -0
  47. package/dist/src/internal/cn.d.ts +1 -0
  48. package/dist/src/internal/cn.js +3 -0
  49. package/dist/src/message-panel.d.ts +4 -0
  50. package/dist/src/message-panel.js +144 -0
  51. package/dist/src/message.d.ts +36 -0
  52. package/dist/src/message.js +83 -0
  53. package/dist/src/parts/index.d.ts +187 -0
  54. package/dist/src/parts/index.js +332 -0
  55. package/dist/src/parts/kind.d.ts +3 -0
  56. package/dist/src/parts/kind.js +69 -0
  57. package/dist/src/parts/plan.d.ts +10 -0
  58. package/dist/src/parts/plan.js +40 -0
  59. package/dist/src/parts/primitives.d.ts +70 -0
  60. package/dist/src/parts/primitives.js +153 -0
  61. package/dist/src/registry.d.ts +4 -0
  62. package/dist/src/registry.js +31 -0
  63. package/dist/src/status.d.ts +26 -0
  64. package/dist/src/status.js +74 -0
  65. package/package.json +17 -8
  66. package/demo/camel-chat-showcase.tsx +0 -1265
  67. package/demo/fixtures.ts +0 -292
  68. package/demo/message-panel-chat-showcase.tsx +0 -250
  69. package/demo/message-panel-gallery.tsx +0 -389
  70. package/src/adapters/ai-sdk.ts +0 -32
  71. package/src/camel/camel-chat-messages.tsx +0 -1420
  72. package/src/camel/camel-model-select.tsx +0 -88
  73. package/src/camel/camel-plan-bar.tsx +0 -115
  74. package/src/camel/camel-prompt-input.tsx +0 -540
  75. package/src/camel/camel-tool-presentation.tsx +0 -1270
  76. package/src/camel/camel-turn.ts +0 -315
  77. package/src/camel/turn-summary-bar.tsx +0 -81
  78. package/src/chat-summary-panel.tsx +0 -229
  79. package/src/composer/chat-composer.tsx +0 -216
  80. package/src/composer/composer-attachments.tsx +0 -138
  81. package/src/composer/composer-trigger-popover.tsx +0 -236
  82. package/src/composer/composer.tsx +0 -425
  83. package/src/composer/drop-controller.ts +0 -80
  84. package/src/composer/types.ts +0 -79
  85. package/src/contracts.ts +0 -139
  86. package/src/conversation.tsx +0 -76
  87. package/src/internal/cn.ts +0 -3
  88. package/src/message-panel.tsx +0 -368
  89. package/src/message.tsx +0 -254
  90. package/src/parts/index.tsx +0 -1149
  91. package/src/parts/kind.ts +0 -67
  92. package/src/parts/plan.ts +0 -63
  93. package/src/parts/primitives.tsx +0 -439
  94. package/src/registry.ts +0 -44
  95. package/src/status.tsx +0 -132
  96. /package/{demo/index.ts → dist/demo/index.d.ts} +0 -0
  97. /package/{src/camel/index.ts → dist/src/camel/index.d.ts} +0 -0
  98. /package/{src/composer/index.ts → dist/src/composer/index.d.ts} +0 -0
  99. /package/{src/index.ts → dist/src/index.d.ts} +0 -0
package/src/parts/kind.ts DELETED
@@ -1,67 +0,0 @@
1
- import type { RichPartKind } from "../contracts";
2
-
3
- function objectOf(value: unknown): Record<string, unknown> {
4
- return value != null && typeof value === "object"
5
- ? (value as Record<string, unknown>)
6
- : {};
7
- }
8
-
9
- export function toolNameOfPart(part: unknown): string | null {
10
- const record = objectOf(part);
11
- const type = typeof record.type === "string" ? record.type : "";
12
- if (type === "toolCall") {
13
- return typeof record.name === "string" ? record.name : "tool";
14
- }
15
- if (type === "dynamic-tool") {
16
- return typeof record.toolName === "string" ? record.toolName : "dynamic";
17
- }
18
- return type.startsWith("tool-") ? type.slice(5) : null;
19
- }
20
-
21
- export function resolvePartKind(part: unknown): RichPartKind | null {
22
- const record = objectOf(part);
23
- const type = typeof record.type === "string" ? record.type : "";
24
- const toolName = toolNameOfPart(part);
25
- if (type === "thinking") return "reasoning";
26
- if (type === "tool") return "tool";
27
- if (toolName === "update_plan") return "plan";
28
- if (toolName === "ask_user") return "ask_user";
29
- if (toolName === "suggest_followups") return "suggestions";
30
- if (toolName === "schedule") return "schedule";
31
- if (toolName === "parallel") return "parallel";
32
- if (
33
- toolName === "subagents" ||
34
- toolName === "run_agent" ||
35
- toolName === "run_temporary_agent" ||
36
- toolName === "fanout" ||
37
- toolName === "extract" ||
38
- toolName === "dispatch_background"
39
- ) {
40
- return "subagents";
41
- }
42
- if (toolName) return "tool";
43
- const normalized = type.startsWith("data-")
44
- ? type.slice(5).replaceAll("-", "_")
45
- : type;
46
- return [
47
- "text",
48
- "reasoning",
49
- "error",
50
- "image",
51
- "chart",
52
- "agent_card",
53
- "plan",
54
- "schedule",
55
- "parallel",
56
- "subagents",
57
- "confirmation",
58
- "file",
59
- "dashboard",
60
- "ask_user",
61
- "suggestions",
62
- ].includes(normalized)
63
- ? (normalized as RichPartKind)
64
- : type === "reasoning-file"
65
- ? "file"
66
- : null;
67
- }
package/src/parts/plan.ts DELETED
@@ -1,63 +0,0 @@
1
- import { resolvePartKind } from "./kind";
2
-
3
- type UnknownRecord = Record<string, unknown>;
4
-
5
- export type PlanStepStatus = "pending" | "in_progress" | "done";
6
-
7
- export interface PlanStep {
8
- text: string;
9
- status?: PlanStepStatus;
10
- }
11
-
12
- function recordOf(value: unknown): UnknownRecord {
13
- return value != null && typeof value === "object"
14
- ? (value as UnknownRecord)
15
- : {};
16
- }
17
-
18
- export function planStepsOfPart(part: unknown): PlanStep[] {
19
- const record = recordOf(part);
20
- const data = recordOf(
21
- record.data ?? record.input ?? record.arguments ?? record,
22
- );
23
- if (!Array.isArray(data.steps)) return [];
24
-
25
- return data.steps.flatMap((step) => {
26
- const value = recordOf(step);
27
- const text = typeof value.text === "string" ? value.text.trim() : "";
28
- if (!text) return [];
29
- const status =
30
- value.status === "in_progress" || value.status === "done"
31
- ? value.status
32
- : "pending";
33
- return [{ text, status }];
34
- });
35
- }
36
-
37
- export function projectLatestPlan(
38
- messages: readonly {
39
- role: string;
40
- parts: readonly unknown[];
41
- }[],
42
- ): PlanStep[] | null {
43
- for (
44
- let messageIndex = messages.length - 1;
45
- messageIndex >= 0;
46
- messageIndex -= 1
47
- ) {
48
- const message = messages[messageIndex];
49
- if (message.role !== "assistant") continue;
50
- for (
51
- let partIndex = message.parts.length - 1;
52
- partIndex >= 0;
53
- partIndex -= 1
54
- ) {
55
- const part = message.parts[partIndex];
56
- if (resolvePartKind(part) !== "plan") continue;
57
- const steps = planStepsOfPart(part);
58
- if (steps.length > 0) return steps;
59
- if (recordOf(part).state !== "input-streaming") return null;
60
- }
61
- }
62
- return null;
63
- }
@@ -1,439 +0,0 @@
1
- import {
2
- BookOpenIcon,
3
- BrainIcon,
4
- CheckCircle2Icon,
5
- ChevronDownIcon,
6
- ChevronRightIcon,
7
- CircleIcon,
8
- ClockIcon,
9
- WrenchIcon,
10
- XCircleIcon,
11
- } from "lucide-react";
12
- import {
13
- createContext,
14
- forwardRef,
15
- useContext,
16
- useMemo,
17
- useState,
18
- type ButtonHTMLAttributes,
19
- type DetailsHTMLAttributes,
20
- type HTMLAttributes,
21
- type ReactNode,
22
- } from "react";
23
- import type { AIToolPart } from "../contracts";
24
- import { cn } from "../internal/cn";
25
- import { MessageResponse } from "../message";
26
-
27
- interface ReasoningContextValue {
28
- open: boolean;
29
- toggle: () => void;
30
- isStreaming: boolean;
31
- duration?: number;
32
- }
33
-
34
- const ReasoningContext = createContext<ReasoningContextValue | null>(null);
35
-
36
- export interface ReasoningProps extends HTMLAttributes<HTMLDivElement> {
37
- isStreaming?: boolean;
38
- open?: boolean;
39
- defaultOpen?: boolean;
40
- onOpenChange?: (open: boolean) => void;
41
- duration?: number;
42
- }
43
-
44
- export function Reasoning({
45
- isStreaming = false,
46
- open,
47
- defaultOpen,
48
- onOpenChange,
49
- duration,
50
- className,
51
- children,
52
- ...props
53
- }: ReasoningProps) {
54
- const [internalOpen, setInternalOpen] = useState(defaultOpen ?? isStreaming);
55
- const resolvedOpen = open ?? internalOpen;
56
- const toggle = () => {
57
- const next = !resolvedOpen;
58
- if (open === undefined) setInternalOpen(next);
59
- onOpenChange?.(next);
60
- };
61
- const value = useMemo(
62
- () => ({ open: resolvedOpen, toggle, isStreaming, duration }),
63
- [resolvedOpen, isStreaming, duration],
64
- );
65
- return (
66
- <ReasoningContext.Provider value={value}>
67
- <div className={cn("sb-reasoning", className)} {...props}>
68
- {children}
69
- </div>
70
- </ReasoningContext.Provider>
71
- );
72
- }
73
-
74
- export function ReasoningTrigger({
75
- children,
76
- className,
77
- ...props
78
- }: ButtonHTMLAttributes<HTMLButtonElement>) {
79
- const context = useContext(ReasoningContext);
80
- if (!context) throw new Error("ReasoningTrigger must be used within Reasoning");
81
- const label = context.isStreaming
82
- ? "Thinking…"
83
- : context.duration
84
- ? `Thought for ${context.duration}s`
85
- : "Reasoning";
86
- return (
87
- <button
88
- type="button"
89
- className={cn("sb-reasoning__trigger", className)}
90
- onClick={context.toggle}
91
- aria-expanded={context.open}
92
- {...props}
93
- >
94
- {children ?? (
95
- <>
96
- <BrainIcon size={16} />
97
- <span>{label}</span>
98
- <ChevronDownIcon size={16} aria-hidden="true" />
99
- </>
100
- )}
101
- </button>
102
- );
103
- }
104
-
105
- export function ReasoningContent({
106
- children,
107
- className,
108
- ...props
109
- }: Omit<HTMLAttributes<HTMLDivElement>, "children"> & { children: string }) {
110
- const context = useContext(ReasoningContext);
111
- if (!context) throw new Error("ReasoningContent must be used within Reasoning");
112
- if (!context.open) return null;
113
- return (
114
- <div className={cn("sb-reasoning__content", className)} {...props}>
115
- <MessageResponse streaming={context.isStreaming}>{children}</MessageResponse>
116
- </div>
117
- );
118
- }
119
-
120
- type ToolPart = AIToolPart;
121
- export type ToolState = ToolPart["state"];
122
-
123
- const toolStatus: Record<
124
- ToolState,
125
- { label: string; icon: ReactNode; tone: string }
126
- > = {
127
- "approval-requested": {
128
- label: "Awaiting approval",
129
- icon: <ClockIcon size={14} />,
130
- tone: "warning",
131
- },
132
- "approval-responded": {
133
- label: "Responded",
134
- icon: <CheckCircle2Icon size={14} />,
135
- tone: "info",
136
- },
137
- "input-available": {
138
- label: "Running",
139
- icon: <ClockIcon size={14} />,
140
- tone: "running",
141
- },
142
- "input-streaming": {
143
- label: "Pending",
144
- icon: <CircleIcon size={14} />,
145
- tone: "running",
146
- },
147
- "output-available": {
148
- label: "Completed",
149
- icon: <CheckCircle2Icon size={14} />,
150
- tone: "success",
151
- },
152
- "output-denied": {
153
- label: "Denied",
154
- icon: <XCircleIcon size={14} />,
155
- tone: "warning",
156
- },
157
- "output-error": {
158
- label: "Error",
159
- icon: <XCircleIcon size={14} />,
160
- tone: "error",
161
- },
162
- };
163
-
164
- export function getStatusBadge(status: ToolState) {
165
- const value = toolStatus[status];
166
- return (
167
- <span className="sb-tool-line__status" data-tone={value.tone}>
168
- {value.icon}
169
- {value.label}
170
- </span>
171
- );
172
- }
173
-
174
- export const Tool = forwardRef<
175
- HTMLDetailsElement,
176
- DetailsHTMLAttributes<HTMLDetailsElement>
177
- >(({ className, ...props }, ref) => (
178
- <details ref={ref} className={cn("sb-tool-line", className)} {...props} />
179
- ));
180
- Tool.displayName = "Tool";
181
-
182
- type ToolHeaderProps = HTMLAttributes<HTMLElement> & {
183
- title?: string;
184
- type: ToolPart["type"];
185
- state: ToolState;
186
- toolName?: string;
187
- };
188
-
189
- export function ToolHeader({
190
- className,
191
- title,
192
- type,
193
- state,
194
- toolName,
195
- ...props
196
- }: ToolHeaderProps) {
197
- const derived =
198
- type === "dynamic-tool"
199
- ? toolName ?? "tool"
200
- : type.split("-").slice(1).join("-");
201
- return (
202
- <summary className={cn("sb-tool-line__trigger", className)} {...props}>
203
- <WrenchIcon size={16} />
204
- <span>{title ?? derived}</span>
205
- {state !== "output-available" && getStatusBadge(state)}
206
- <ChevronRightIcon
207
- size={16}
208
- className="sb-tool-line__chevron"
209
- aria-hidden="true"
210
- />
211
- </summary>
212
- );
213
- }
214
-
215
- export const ToolContent = forwardRef<
216
- HTMLDivElement,
217
- HTMLAttributes<HTMLDivElement>
218
- >(({ className, ...props }, ref) => (
219
- <div ref={ref} className={cn("sb-tool-line__details", className)} {...props} />
220
- ));
221
- ToolContent.displayName = "ToolContent";
222
-
223
- export function ToolInput({
224
- input,
225
- className,
226
- ...props
227
- }: HTMLAttributes<HTMLPreElement> & { input: unknown }) {
228
- return (
229
- <pre className={cn("sb-terminal", className)} {...props}>
230
- {JSON.stringify(input, null, 2)}
231
- </pre>
232
- );
233
- }
234
-
235
- export function ToolOutput({
236
- output,
237
- errorText,
238
- className,
239
- ...props
240
- }: HTMLAttributes<HTMLPreElement> & {
241
- output: unknown;
242
- errorText?: string;
243
- }) {
244
- if (output == null && !errorText) return null;
245
- const content =
246
- typeof output === "string"
247
- ? output
248
- : output == null
249
- ? ""
250
- : JSON.stringify(output, null, 2);
251
- return (
252
- <pre
253
- className={cn("sb-terminal", className)}
254
- data-error={errorText ? "true" : undefined}
255
- {...props}
256
- >
257
- {errorText || content}
258
- </pre>
259
- );
260
- }
261
-
262
- interface ConfirmationContextValue {
263
- state: ToolState | "pending" | "confirmed" | "cancelled";
264
- approval?: { id: string; approved?: boolean };
265
- }
266
-
267
- const ConfirmationContext = createContext<ConfirmationContextValue | null>(null);
268
-
269
- export interface ConfirmationProps extends HTMLAttributes<HTMLDivElement> {
270
- state: ToolState | "pending" | "confirmed" | "cancelled";
271
- approval?: { id: string; approved?: boolean };
272
- }
273
-
274
- export function Confirmation({
275
- state,
276
- approval,
277
- className,
278
- ...props
279
- }: ConfirmationProps) {
280
- const value = useMemo(() => ({ state, approval }), [state, approval]);
281
- return (
282
- <ConfirmationContext.Provider value={value}>
283
- <div className={cn("sb-confirmation", className)} {...props} />
284
- </ConfirmationContext.Provider>
285
- );
286
- }
287
-
288
- function useConfirmation() {
289
- const context = useContext(ConfirmationContext);
290
- if (!context) throw new Error("Confirmation child must be used within Confirmation");
291
- return context;
292
- }
293
-
294
- export const ConfirmationTitle = ({
295
- className,
296
- ...props
297
- }: HTMLAttributes<HTMLDivElement>) => (
298
- <div className={cn("sb-confirmation__title", className)} {...props} />
299
- );
300
-
301
- export function ConfirmationRequest({ children }: { children?: ReactNode }) {
302
- const { state } = useConfirmation();
303
- return state === "approval-requested" || state === "pending" ? children : null;
304
- }
305
-
306
- export function ConfirmationAccepted({ children }: { children?: ReactNode }) {
307
- const { state, approval } = useConfirmation();
308
- return state === "confirmed" || approval?.approved === true ? children : null;
309
- }
310
-
311
- export function ConfirmationRejected({ children }: { children?: ReactNode }) {
312
- const { state, approval } = useConfirmation();
313
- return state === "cancelled" ||
314
- state === "output-denied" ||
315
- approval?.approved === false
316
- ? children
317
- : null;
318
- }
319
-
320
- export function ConfirmationActions({
321
- className,
322
- ...props
323
- }: HTMLAttributes<HTMLDivElement>) {
324
- const { state } = useConfirmation();
325
- if (state !== "approval-requested" && state !== "pending") return null;
326
- return (
327
- <div
328
- className={cn("sb-confirmation__actions", className)}
329
- {...props}
330
- />
331
- );
332
- }
333
-
334
- export const ConfirmationAction = forwardRef<
335
- HTMLButtonElement,
336
- ButtonHTMLAttributes<HTMLButtonElement>
337
- >(({ className, ...props }, ref) => (
338
- <button
339
- ref={ref}
340
- type="button"
341
- className={cn("sb-button", className)}
342
- {...props}
343
- />
344
- ));
345
- ConfirmationAction.displayName = "ConfirmationAction";
346
-
347
- export const Plan = forwardRef<
348
- HTMLDivElement,
349
- HTMLAttributes<HTMLDivElement> & {
350
- isStreaming?: boolean;
351
- }
352
- >(({ className, isStreaming, ...props }, ref) => (
353
- <div
354
- ref={ref}
355
- className={cn("sb-plan", className)}
356
- data-streaming={isStreaming ? "true" : undefined}
357
- {...props}
358
- />
359
- ));
360
- Plan.displayName = "Plan";
361
-
362
- export const PlanHeader = ({
363
- className,
364
- ...props
365
- }: HTMLAttributes<HTMLDivElement>) => (
366
- <div className={cn("sb-plan__header", className)} {...props} />
367
- );
368
- export const PlanTitle = ({
369
- className,
370
- ...props
371
- }: HTMLAttributes<HTMLHeadingElement>) => (
372
- <h3 className={cn("sb-plan__title", className)} {...props} />
373
- );
374
- export const PlanDescription = ({
375
- className,
376
- ...props
377
- }: HTMLAttributes<HTMLParagraphElement>) => (
378
- <p className={cn("sb-plan__description", className)} {...props} />
379
- );
380
- export const PlanContent = ({
381
- className,
382
- ...props
383
- }: HTMLAttributes<HTMLDivElement>) => (
384
- <div className={cn("sb-plan__content", className)} {...props} />
385
- );
386
- export const PlanFooter = ({
387
- className,
388
- ...props
389
- }: HTMLAttributes<HTMLDivElement>) => (
390
- <div className={cn("sb-plan__footer", className)} {...props} />
391
- );
392
-
393
- export const Sources = forwardRef<
394
- HTMLDetailsElement,
395
- DetailsHTMLAttributes<HTMLDetailsElement>
396
- >(({ className, ...props }, ref) => (
397
- <details ref={ref} className={cn("sb-sources", className)} {...props} />
398
- ));
399
- Sources.displayName = "Sources";
400
-
401
- export function SourcesTrigger({
402
- count,
403
- children,
404
- className,
405
- ...props
406
- }: HTMLAttributes<HTMLElement> & { count: number }) {
407
- return (
408
- <summary className={cn("sb-sources__trigger", className)} {...props}>
409
- {children ?? (
410
- <>
411
- <span>来源 {count} 个</span>
412
- <ChevronDownIcon size={15} />
413
- </>
414
- )}
415
- </summary>
416
- );
417
- }
418
- export const SourcesContent = ({
419
- className,
420
- ...props
421
- }: HTMLAttributes<HTMLDivElement>) => (
422
- <div className={cn("sb-sources__content", className)} {...props} />
423
- );
424
- export function Source({
425
- title,
426
- children,
427
- ...props
428
- }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { title?: string }) {
429
- return (
430
- <a target="_blank" rel="noreferrer" {...props}>
431
- {children ?? (
432
- <>
433
- <BookOpenIcon size={14} />
434
- <span>{title}</span>
435
- </>
436
- )}
437
- </a>
438
- );
439
- }
package/src/registry.ts DELETED
@@ -1,44 +0,0 @@
1
- import type {
2
- PartRendererComponent,
3
- PartRendererDefinition,
4
- PartRendererRegistry,
5
- } from "./contracts";
6
- import { DefaultPartRenderer } from "./parts";
7
-
8
- export function createPartRendererRegistry(
9
- definitions: readonly PartRendererDefinition[],
10
- ): PartRendererRegistry {
11
- const stable = [...definitions];
12
- return {
13
- names: stable.map((definition) => definition.name),
14
- resolve(part: unknown): PartRendererComponent | undefined {
15
- return stable.find((definition) => definition.match(part))?.component;
16
- },
17
- };
18
- }
19
-
20
- export function mergePartRendererRegistries(
21
- ...registries: Array<PartRendererRegistry | undefined>
22
- ): PartRendererRegistry {
23
- const resolved = registries.filter(
24
- (registry): registry is PartRendererRegistry => registry !== undefined,
25
- );
26
- return {
27
- names: resolved.flatMap((registry) => registry.names),
28
- resolve(part: unknown) {
29
- for (const registry of resolved) {
30
- const component = registry.resolve(part);
31
- if (component) return component;
32
- }
33
- return undefined;
34
- },
35
- };
36
- }
37
-
38
- export const defaultPartRendererRegistry = createPartRendererRegistry([
39
- {
40
- name: "default",
41
- match: () => true,
42
- component: DefaultPartRenderer,
43
- },
44
- ]);