@vantageos/mosaic 0.1.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/index.cjs ADDED
@@ -0,0 +1,699 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+ var reactVirtual = require('@tanstack/react-virtual');
7
+ var DOMPurify = require('dompurify');
8
+ var marked = require('marked');
9
+ var rxjs = require('rxjs');
10
+ var server = require('@mcp-ui/server');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
15
+
16
+ // src/i18n/strings.ts
17
+ var EN = {
18
+ "ProgressBar.aria.label": "Progress",
19
+ "ProgressBar.error.invalidProps": "ProgressBar: invalid props",
20
+ "ConfirmDialog.button.confirm": "Confirm",
21
+ "ConfirmDialog.button.cancel": "Cancel",
22
+ "ConfirmDialog.aria.dialog": "Confirmation dialog",
23
+ "TableView.aria.table": "Data table",
24
+ "TableView.pagination.next": "Next page",
25
+ "TableView.pagination.prev": "Previous page",
26
+ "TableView.empty.message": "No data to display",
27
+ "TableView.error.invalidProps": "TableView: invalid props",
28
+ "MarkdownRenderer.aria.content": "Rendered Markdown content",
29
+ "MarkdownRenderer.error.invalidProps": "MarkdownRenderer: invalid props",
30
+ "MarkdownRenderer.error.tooLong": "Content exceeds maximum length",
31
+ "TokenDisplayOnceModal.button.copy": "Copy token",
32
+ "TokenDisplayOnceModal.button.close": "Close",
33
+ "TokenDisplayOnceModal.warning.once": "This token will only be shown once. Copy it now.",
34
+ "TokenDisplayOnceModal.copied": "Copied",
35
+ "StatusBadge.aria.status": "Status",
36
+ "StatusBadge.error.invalidProps": "StatusBadge: invalid props"
37
+ };
38
+ var FR = {
39
+ "ProgressBar.aria.label": "Progression",
40
+ "ProgressBar.error.invalidProps": "ProgressBar : propri\xE9t\xE9s invalides",
41
+ "ConfirmDialog.button.confirm": "Confirmer",
42
+ "ConfirmDialog.button.cancel": "Annuler",
43
+ "ConfirmDialog.aria.dialog": "Bo\xEEte de dialogue de confirmation",
44
+ "TableView.aria.table": "Tableau de donn\xE9es",
45
+ "TableView.pagination.next": "Page suivante",
46
+ "TableView.pagination.prev": "Page pr\xE9c\xE9dente",
47
+ "TableView.empty.message": "Aucune donn\xE9e \xE0 afficher",
48
+ "TableView.error.invalidProps": "TableView : propri\xE9t\xE9s invalides",
49
+ "MarkdownRenderer.aria.content": "Contenu Markdown rendu",
50
+ "MarkdownRenderer.error.invalidProps": "MarkdownRenderer : propri\xE9t\xE9s invalides",
51
+ "MarkdownRenderer.error.tooLong": "Le contenu d\xE9passe la longueur maximale",
52
+ "TokenDisplayOnceModal.button.copy": "Copier le jeton",
53
+ "TokenDisplayOnceModal.button.close": "Fermer",
54
+ "TokenDisplayOnceModal.warning.once": "Ce jeton ne sera affich\xE9 qu'une seule fois. Copiez-le maintenant.",
55
+ "TokenDisplayOnceModal.copied": "Copi\xE9",
56
+ "StatusBadge.aria.status": "Statut",
57
+ "StatusBadge.error.invalidProps": "StatusBadge : propri\xE9t\xE9s invalides"
58
+ };
59
+ var TABLE = {
60
+ en: EN,
61
+ fr: FR
62
+ };
63
+ function t(key, locale = "en") {
64
+ return TABLE[locale]?.[key] ?? TABLE.en[key];
65
+ }
66
+ var ProgressBarPropsSchema = zod.z.object({
67
+ value: zod.z.number().min(0).max(100),
68
+ label: zod.z.string().min(1),
69
+ locale: zod.z.enum(["en", "fr"]).default("en"),
70
+ colorVariant: zod.z.enum(["default", "warning", "danger"]).default("default")
71
+ });
72
+ function validateProps(raw) {
73
+ return ProgressBarPropsSchema.parse(raw);
74
+ }
75
+ function pickLocale(raw) {
76
+ return raw.locale === "fr" ? "fr" : "en";
77
+ }
78
+ var colorVariantClasses = {
79
+ default: "bg-blue-600",
80
+ warning: "bg-amber-500",
81
+ danger: "bg-red-600"
82
+ };
83
+ var containerVariantClasses = {
84
+ default: "progressbar-default",
85
+ warning: "progressbar-warning",
86
+ danger: "progressbar-danger"
87
+ };
88
+ function ProgressBarInner(props) {
89
+ const { value, label, colorVariant } = props;
90
+ const fillClass = colorVariantClasses[colorVariant];
91
+ const containerClass = containerVariantClasses[colorVariant];
92
+ return /* @__PURE__ */ jsxRuntime.jsxs(
93
+ "div",
94
+ {
95
+ role: "progressbar",
96
+ "aria-valuenow": value,
97
+ "aria-valuemin": 0,
98
+ "aria-valuemax": 100,
99
+ "aria-label": label,
100
+ tabIndex: 0,
101
+ className: `mosaic-progress ${containerClass}`,
102
+ style: { width: "100%" },
103
+ children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx(
105
+ "div",
106
+ {
107
+ "data-testid": "progress-fill",
108
+ className: `mosaic-progress-fill ${fillClass}`,
109
+ style: { width: `${value}%` }
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mosaic-progress-label", children: [
113
+ label,
114
+ ": ",
115
+ value,
116
+ "%"
117
+ ] })
118
+ ]
119
+ }
120
+ );
121
+ }
122
+ function ProgressBar(raw) {
123
+ try {
124
+ const props = validateProps(raw);
125
+ return /* @__PURE__ */ jsxRuntime.jsx(ProgressBarInner, { ...props });
126
+ } catch {
127
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", className: "mosaic-progress-error", children: t("ProgressBar.error.invalidProps", pickLocale(raw)) });
128
+ }
129
+ }
130
+ var ConfirmDialogPropsSchema = zod.z.object({
131
+ title: zod.z.string().min(1),
132
+ message: zod.z.string().min(1),
133
+ confirmLabel: zod.z.string().min(1),
134
+ cancelLabel: zod.z.string().min(1),
135
+ variant: zod.z.enum(["default", "danger"]).default("default"),
136
+ locale: zod.z.enum(["en", "fr"]).default("en")
137
+ });
138
+ function validateConfirmDialogProps(raw) {
139
+ return ConfirmDialogPropsSchema.parse(raw);
140
+ }
141
+ function ConfirmDialogInner({
142
+ title,
143
+ message,
144
+ confirmLabel,
145
+ cancelLabel,
146
+ variant,
147
+ onConfirm,
148
+ onCancel
149
+ }) {
150
+ const dialogRef = react.useRef(null);
151
+ const titleId = react.useId();
152
+ const messageId = react.useId();
153
+ react.useEffect(() => {
154
+ const dialog = dialogRef.current;
155
+ if (!dialog) return;
156
+ dialog.showModal();
157
+ return () => {
158
+ if (dialog.open) dialog.close();
159
+ };
160
+ }, []);
161
+ function handleKeyDown(e) {
162
+ if (e.key === "Escape") {
163
+ e.preventDefault();
164
+ onCancel();
165
+ }
166
+ }
167
+ function handleBackdropClick(e) {
168
+ if (e.target === dialogRef.current) {
169
+ onCancel();
170
+ }
171
+ }
172
+ const confirmClassName = variant === "danger" ? "bg-red-600 text-white hover:bg-red-700 focus-visible:outline-red-600 px-4 py-2 rounded-md text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" : "bg-gray-900 text-white hover:bg-gray-700 focus-visible:outline-gray-900 px-4 py-2 rounded-md text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2";
173
+ return /* @__PURE__ */ jsxRuntime.jsx(
174
+ "dialog",
175
+ {
176
+ ref: dialogRef,
177
+ "aria-modal": "true",
178
+ "aria-labelledby": titleId,
179
+ "aria-describedby": messageId,
180
+ onKeyDown: handleKeyDown,
181
+ onClick: handleBackdropClick,
182
+ className: "rounded-lg shadow-xl p-0 backdrop:bg-black/40 w-full max-w-md mx-auto",
183
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
184
+ "div",
185
+ {
186
+ className: "p-6 flex flex-col gap-4",
187
+ onClick: (e) => e.stopPropagation(),
188
+ onKeyDown: (e) => e.stopPropagation(),
189
+ children: [
190
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "text-lg font-semibold text-gray-900", children: title }),
191
+ /* @__PURE__ */ jsxRuntime.jsx("p", { id: messageId, className: "text-sm text-gray-600", children: message }),
192
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 mt-2", children: [
193
+ /* @__PURE__ */ jsxRuntime.jsx(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ onClick: onCancel,
198
+ className: "px-4 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400 transition-colors",
199
+ children: cancelLabel
200
+ }
201
+ ),
202
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onConfirm, className: confirmClassName, children: confirmLabel })
203
+ ] })
204
+ ]
205
+ }
206
+ )
207
+ }
208
+ );
209
+ }
210
+ function ConfirmDialog(raw) {
211
+ try {
212
+ const parsed = validateConfirmDialogProps(raw);
213
+ return /* @__PURE__ */ jsxRuntime.jsx(ConfirmDialogInner, { ...parsed, onConfirm: raw.onConfirm, onCancel: raw.onCancel });
214
+ } catch (err) {
215
+ console.error("[ConfirmDialog] Invalid props \u2014 component will not render.", err);
216
+ return null;
217
+ }
218
+ }
219
+ var ColumnDefSchema = zod.z.object({
220
+ key: zod.z.string().min(1),
221
+ header: zod.z.string().min(1)
222
+ // render is a runtime function — excluded from Zod, typed separately
223
+ });
224
+ var TableViewPropsSchema = zod.z.object({
225
+ columns: zod.z.array(ColumnDefSchema).min(1, "TableView requires at least one column"),
226
+ virtualizeThreshold: zod.z.number().int().positive().default(100),
227
+ ariaLabel: zod.z.string().min(1),
228
+ locale: zod.z.enum(["en", "fr"]).default("en")
229
+ });
230
+ function validateTableViewProps(raw) {
231
+ return TableViewPropsSchema.parse(raw);
232
+ }
233
+ var OVERSCAN = 5;
234
+ function TableView(props) {
235
+ let validatedThreshold = 100;
236
+ try {
237
+ const validated = validateTableViewProps({
238
+ columns: props.columns.map(({ key, header }) => ({ key, header })),
239
+ virtualizeThreshold: props.virtualizeThreshold,
240
+ ariaLabel: props.ariaLabel,
241
+ locale: props.locale
242
+ });
243
+ validatedThreshold = validated.virtualizeThreshold;
244
+ } catch {
245
+ const locale = props.locale === "fr" ? "fr" : "en";
246
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
247
+ }
248
+ return /* @__PURE__ */ jsxRuntime.jsx(TableViewInner, { ...props, virtualizeThreshold: validatedThreshold });
249
+ }
250
+ function TableViewInner({
251
+ columns,
252
+ rows$,
253
+ virtualizeThreshold = 100,
254
+ ariaLabel,
255
+ locale
256
+ }) {
257
+ const resolvedLocale = locale === "fr" ? "fr" : "en";
258
+ const [rows, setRows] = react.useState([]);
259
+ react.useEffect(() => {
260
+ setRows([]);
261
+ const sub = rows$.subscribe((chunk) => {
262
+ setRows((prev) => [...prev, ...chunk]);
263
+ });
264
+ return () => {
265
+ sub.unsubscribe();
266
+ };
267
+ }, [rows$]);
268
+ const isVirtual = rows.length > virtualizeThreshold;
269
+ if (isVirtual) {
270
+ return /* @__PURE__ */ jsxRuntime.jsx(
271
+ VirtualTable,
272
+ {
273
+ rows,
274
+ columns,
275
+ ariaLabel,
276
+ locale: resolvedLocale
277
+ }
278
+ );
279
+ }
280
+ return /* @__PURE__ */ jsxRuntime.jsx(
281
+ StaticTable,
282
+ {
283
+ rows,
284
+ columns,
285
+ ariaLabel,
286
+ locale: resolvedLocale
287
+ }
288
+ );
289
+ }
290
+ function renderCell(col, row) {
291
+ if (col.render) {
292
+ return col.render(row);
293
+ }
294
+ return String(row[col.key] ?? "");
295
+ }
296
+ function StaticTable({
297
+ rows,
298
+ columns,
299
+ ariaLabel,
300
+ locale
301
+ }) {
302
+ const emptyMsg = t("TableView.empty.message", locale);
303
+ return /* @__PURE__ */ jsxRuntime.jsxs(
304
+ "table",
305
+ {
306
+ "aria-label": ariaLabel,
307
+ "aria-rowcount": rows.length,
308
+ style: { width: "100%", borderCollapse: "collapse" },
309
+ children: [
310
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
311
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: columns.length, "aria-label": emptyMsg, children: emptyMsg }) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
312
+ "tr",
313
+ {
314
+ "aria-rowindex": rowIndex + 2,
315
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(col, row) }, col.key))
316
+ },
317
+ row.id != null ? String(row.id) : `row-${rowIndex}`
318
+ )) })
319
+ ]
320
+ }
321
+ );
322
+ }
323
+ function VirtualTable({
324
+ rows,
325
+ columns,
326
+ ariaLabel,
327
+ // locale reserved for future virtual-mode empty/pagination strings
328
+ locale: _locale
329
+ }) {
330
+ const scrollRef = react.useRef(null);
331
+ const rowVirtualizer = reactVirtual.useVirtualizer({
332
+ count: rows.length,
333
+ /* v8 ignore next 1 */
334
+ getScrollElement: () => scrollRef.current,
335
+ estimateSize: react.useCallback(() => 40, []),
336
+ overscan: OVERSCAN
337
+ });
338
+ const virtualItems = rowVirtualizer.getVirtualItems();
339
+ const totalSize = rowVirtualizer.getTotalSize();
340
+ return /* @__PURE__ */ jsxRuntime.jsx(
341
+ "div",
342
+ {
343
+ "data-virtual": "true",
344
+ ref: scrollRef,
345
+ style: { height: 400, overflow: "auto", position: "relative" },
346
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
347
+ "table",
348
+ {
349
+ "aria-label": ariaLabel,
350
+ "aria-rowcount": rows.length,
351
+ style: { width: "100%", borderCollapse: "collapse" },
352
+ children: [
353
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
354
+ /* @__PURE__ */ jsxRuntime.jsx(
355
+ "tbody",
356
+ {
357
+ style: {
358
+ height: `${totalSize}px`,
359
+ position: "relative",
360
+ display: "block"
361
+ },
362
+ children: virtualItems.map((virtualRow) => {
363
+ const row = rows[virtualRow.index] ?? {};
364
+ return /* @__PURE__ */ jsxRuntime.jsx(
365
+ "tr",
366
+ {
367
+ "aria-rowindex": virtualRow.index + 2,
368
+ style: {
369
+ position: "absolute",
370
+ top: 0,
371
+ left: 0,
372
+ width: "100%",
373
+ transform: `translateY(${virtualRow.start}px)`,
374
+ display: "flex"
375
+ },
376
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1 }, children: renderCell(col, row) }, col.key))
377
+ },
378
+ row.id != null ? String(row.id) : `vrow-${virtualRow.index}`
379
+ );
380
+ })
381
+ }
382
+ )
383
+ ]
384
+ }
385
+ )
386
+ }
387
+ );
388
+ }
389
+ var MarkdownRendererPropsSchema = zod.z.object({
390
+ content: zod.z.string(),
391
+ locale: zod.z.enum(["en", "fr"]),
392
+ maxLength: zod.z.number().positive().default(5e4),
393
+ allowHtml: zod.z.boolean().default(false)
394
+ });
395
+ function validateProps2(raw) {
396
+ return MarkdownRendererPropsSchema.parse(raw);
397
+ }
398
+ var tooLongMessages = {
399
+ en: "Content exceeds maximum length.",
400
+ fr: "Le contenu d\xE9passe la longueur maximale."
401
+ };
402
+ function MarkdownRendererInner({
403
+ content,
404
+ locale,
405
+ maxLength,
406
+ allowHtml
407
+ }) {
408
+ if (content.length > maxLength) {
409
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", lang: locale, children: tooLongMessages[locale] });
410
+ }
411
+ const source = allowHtml ? content : content.replace(/<[^>]*>/g, "");
412
+ const rawHtml = marked.marked.parse(source, { async: false });
413
+ const cleanHtml = DOMPurify__default.default.sanitize(rawHtml);
414
+ const ariaLabel = locale === "fr" ? "Contenu format\xE9" : "Formatted content";
415
+ return /* @__PURE__ */ jsxRuntime.jsx(
416
+ "article",
417
+ {
418
+ "aria-label": ariaLabel,
419
+ lang: locale,
420
+ dangerouslySetInnerHTML: { __html: cleanHtml }
421
+ }
422
+ );
423
+ }
424
+ function MarkdownRenderer(raw) {
425
+ try {
426
+ const props = validateProps2(raw);
427
+ return /* @__PURE__ */ jsxRuntime.jsx(MarkdownRendererInner, { ...props });
428
+ } catch {
429
+ const locale = raw.locale === "fr" ? "fr" : "en";
430
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("MarkdownRenderer.error.invalidProps", locale) });
431
+ }
432
+ }
433
+ var TokenDisplayOnceModalPropsSchema = zod.z.object({
434
+ token: zod.z.string().min(1),
435
+ title: zod.z.string().min(1),
436
+ warningMessage: zod.z.string().min(1),
437
+ copyLabel: zod.z.string().min(1),
438
+ closeLabel: zod.z.string().min(1),
439
+ locale: zod.z.enum(["en", "fr"]).default("en")
440
+ });
441
+ function validateTokenDisplayOnceModalProps(raw) {
442
+ return TokenDisplayOnceModalPropsSchema.parse(raw);
443
+ }
444
+ function TokenDisplayOnceModalInner({
445
+ token,
446
+ title,
447
+ warningMessage,
448
+ copyLabel,
449
+ closeLabel,
450
+ locale,
451
+ onClose
452
+ }) {
453
+ const dialogRef = react.useRef(null);
454
+ const titleId = react.useId();
455
+ const warningId = react.useId();
456
+ const [copied, setCopied] = react.useState(false);
457
+ const [secureToken] = react.useState(token);
458
+ react.useEffect(() => {
459
+ const dialog = dialogRef.current;
460
+ if (!dialog) return;
461
+ dialog.showModal();
462
+ return () => {
463
+ if (dialog.open) dialog.close();
464
+ };
465
+ }, []);
466
+ async function handleCopy() {
467
+ try {
468
+ await navigator.clipboard.writeText(secureToken);
469
+ setCopied(true);
470
+ setTimeout(() => setCopied(false), 2e3);
471
+ } catch {
472
+ setCopied(false);
473
+ }
474
+ }
475
+ function handleKeyDown(e) {
476
+ if (e.key === "Escape") {
477
+ e.preventDefault();
478
+ onClose();
479
+ }
480
+ }
481
+ function handleBackdropClick(e) {
482
+ if (e.target === dialogRef.current) {
483
+ onClose();
484
+ }
485
+ }
486
+ return /* @__PURE__ */ jsxRuntime.jsx(
487
+ "dialog",
488
+ {
489
+ ref: dialogRef,
490
+ "aria-modal": "true",
491
+ "aria-labelledby": titleId,
492
+ "aria-describedby": warningId,
493
+ onKeyDown: handleKeyDown,
494
+ onClick: handleBackdropClick,
495
+ className: "rounded-lg shadow-xl p-0 backdrop:bg-black/40 w-full max-w-lg mx-auto",
496
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
497
+ "div",
498
+ {
499
+ className: "p-6 flex flex-col gap-4",
500
+ onClick: (e) => e.stopPropagation(),
501
+ onKeyDown: (e) => e.stopPropagation(),
502
+ children: [
503
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "text-lg font-semibold text-gray-900", children: title }),
504
+ /* @__PURE__ */ jsxRuntime.jsx(
505
+ "p",
506
+ {
507
+ id: warningId,
508
+ className: "text-sm text-amber-700 bg-amber-50 border border-amber-200 rounded-md px-3 py-2",
509
+ children: warningMessage
510
+ }
511
+ ),
512
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 bg-gray-50 border border-gray-200 rounded-md px-3 py-2", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "flex-1 text-sm font-mono text-gray-800 break-all select-all", children: secureToken }) }),
513
+ /* @__PURE__ */ jsxRuntime.jsx("output", { "aria-live": "polite", "aria-atomic": "true", className: "min-h-[1.25rem] block", children: copied && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-green-700 font-medium", children: t("TokenDisplayOnceModal.copied", locale) }) }),
514
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 mt-2", children: [
515
+ /* @__PURE__ */ jsxRuntime.jsx(
516
+ "button",
517
+ {
518
+ type: "button",
519
+ onClick: handleCopy,
520
+ className: "px-4 py-2 rounded-md text-sm font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400 transition-colors",
521
+ children: copyLabel
522
+ }
523
+ ),
524
+ /* @__PURE__ */ jsxRuntime.jsx(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ onClick: onClose,
529
+ className: "px-4 py-2 rounded-md text-sm font-medium bg-gray-900 text-white hover:bg-gray-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 transition-colors",
530
+ children: closeLabel
531
+ }
532
+ )
533
+ ] })
534
+ ]
535
+ }
536
+ )
537
+ }
538
+ );
539
+ }
540
+ function TokenDisplayOnceModal(raw) {
541
+ try {
542
+ const parsed = validateTokenDisplayOnceModalProps(raw);
543
+ return /* @__PURE__ */ jsxRuntime.jsx(TokenDisplayOnceModalInner, { ...parsed, onClose: raw.onClose });
544
+ } catch (err) {
545
+ console.warn("[TokenDisplayOnceModal] Invalid props \u2014 component will not render.", err);
546
+ return null;
547
+ }
548
+ }
549
+ var StatusBadgePropsSchema = zod.z.object({
550
+ status: zod.z.string(),
551
+ variant: zod.z.enum(["success", "warning", "danger", "info", "neutral"]).default("neutral"),
552
+ label: zod.z.string().optional(),
553
+ locale: zod.z.enum(["en", "fr"])
554
+ });
555
+ function validateProps3(raw) {
556
+ return StatusBadgePropsSchema.parse(raw);
557
+ }
558
+ var variantClasses = {
559
+ success: "bg-green-100 text-green-800 ring-1 ring-green-300",
560
+ warning: "bg-amber-100 text-amber-800 ring-1 ring-amber-300",
561
+ danger: "bg-red-100 text-red-800 ring-1 ring-red-300",
562
+ info: "bg-blue-100 text-blue-800 ring-1 ring-blue-300",
563
+ neutral: "bg-slate-100 text-slate-800 ring-1 ring-slate-300"
564
+ };
565
+ function StatusBadgeInner({ status, variant, label, locale }) {
566
+ const ariaLabel = label ?? status;
567
+ return /* @__PURE__ */ jsxRuntime.jsx(
568
+ "output",
569
+ {
570
+ "aria-label": ariaLabel,
571
+ lang: locale,
572
+ className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses[variant]}`,
573
+ children: label ?? status
574
+ }
575
+ );
576
+ }
577
+ function StatusBadge(raw) {
578
+ try {
579
+ const props = validateProps3(raw);
580
+ return /* @__PURE__ */ jsxRuntime.jsx(StatusBadgeInner, { ...props });
581
+ } catch {
582
+ const locale = raw.locale === "fr" ? "fr" : "en";
583
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("StatusBadge.error.invalidProps", locale) });
584
+ }
585
+ }
586
+ function useStreamingHydration(props$) {
587
+ const [state, setState] = react.useState({});
588
+ react.useEffect(() => {
589
+ const sub = props$.subscribe((chunk) => {
590
+ setState((prev) => ({ ...prev, ...chunk }));
591
+ });
592
+ return () => {
593
+ sub.unsubscribe();
594
+ };
595
+ }, [props$]);
596
+ return state;
597
+ }
598
+ var MOSAIC_STREAM_CHUNK_TYPE = "mosaic.stream.chunk";
599
+ function createPostMessageObservable() {
600
+ const subject = new rxjs.Subject();
601
+ if (typeof window !== "undefined") {
602
+ window.addEventListener("message", (event) => {
603
+ const data = event.data;
604
+ if (data?.type === MOSAIC_STREAM_CHUNK_TYPE) {
605
+ subject.next(data.data ?? data.params);
606
+ }
607
+ });
608
+ }
609
+ return subject.asObservable();
610
+ }
611
+ var MCP_UI_CAPABILITY_KEY = "io.modelcontextprotocol/ui";
612
+ var MOSAIC_MIME_TYPE = "text/html;profile=mcp-app";
613
+ var SCHEMA_BY_NAME = {
614
+ ProgressBar: ProgressBarPropsSchema,
615
+ ConfirmDialog: ConfirmDialogPropsSchema,
616
+ TableView: TableViewPropsSchema,
617
+ MarkdownRenderer: MarkdownRendererPropsSchema,
618
+ TokenDisplayOnceModal: TokenDisplayOnceModalPropsSchema,
619
+ StatusBadge: StatusBadgePropsSchema
620
+ };
621
+ var COMPONENT_KEYS = Object.keys(SCHEMA_BY_NAME);
622
+ var I18N_TITLE = {
623
+ ProgressBar: { en: "Progress", fr: "Progression" },
624
+ ConfirmDialog: { en: "Confirmation", fr: "Confirmation" },
625
+ TableView: { en: "Data table", fr: "Tableau de donn\xE9es" },
626
+ MarkdownRenderer: { en: "Markdown content", fr: "Contenu Markdown" },
627
+ TokenDisplayOnceModal: { en: "Token (shown once)", fr: "Jeton (affich\xE9 une fois)" },
628
+ StatusBadge: { en: "Status", fr: "Statut" }
629
+ };
630
+ function escapeHtml(s) {
631
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
632
+ }
633
+ function fallbackMarkdown(componentName, locale) {
634
+ const heading = I18N_TITLE[componentName][locale];
635
+ const note = locale === "fr" ? "Rendu Markdown de secours \u2014 composant Mosaic charg\xE9 via MCP UI." : "Markdown fallback \u2014 Mosaic component rendered via MCP UI.";
636
+ return `# ${heading}
637
+
638
+ ${note}
639
+ `;
640
+ }
641
+ function buildHtml(componentName, props, locale) {
642
+ const propsJson = escapeHtml(JSON.stringify(props));
643
+ const title = escapeHtml(I18N_TITLE[componentName][locale]);
644
+ return `<!DOCTYPE html><html lang="${locale}"><head><meta charset="utf-8"><title>${title}</title></head><body><div id="mosaic-root" data-component="${componentName}" data-props='${propsJson}'></div></body></html>`;
645
+ }
646
+ function createMosaicResource(componentName, props, locale = "en") {
647
+ const schema = SCHEMA_BY_NAME[componentName];
648
+ if (!schema) {
649
+ throw new Error(`createMosaicResource: unknown component '${componentName}'`);
650
+ }
651
+ const validated = schema.parse(props);
652
+ const uri = `ui://mosaic/${componentName.toLowerCase()}`;
653
+ const html = buildHtml(componentName, validated, locale);
654
+ const fallback = fallbackMarkdown(componentName, locale);
655
+ const resource = server.createUIResource({
656
+ uri,
657
+ content: { type: "rawHtml", htmlString: html },
658
+ encoding: "text",
659
+ metadata: {
660
+ ui: {
661
+ resourceUri: uri,
662
+ locale,
663
+ componentName,
664
+ fallback
665
+ }
666
+ }
667
+ });
668
+ resource.resource.mimeType = MOSAIC_MIME_TYPE;
669
+ return resource;
670
+ }
671
+ var MOSAIC_SUPPORTED_COMPONENTS = COMPONENT_KEYS;
672
+
673
+ exports.ConfirmDialog = ConfirmDialog;
674
+ exports.ConfirmDialogPropsSchema = ConfirmDialogPropsSchema;
675
+ exports.MCP_UI_CAPABILITY_KEY = MCP_UI_CAPABILITY_KEY;
676
+ exports.MOSAIC_MIME_TYPE = MOSAIC_MIME_TYPE;
677
+ exports.MOSAIC_STREAM_CHUNK_TYPE = MOSAIC_STREAM_CHUNK_TYPE;
678
+ exports.MOSAIC_SUPPORTED_COMPONENTS = MOSAIC_SUPPORTED_COMPONENTS;
679
+ exports.MarkdownRenderer = MarkdownRenderer;
680
+ exports.MarkdownRendererPropsSchema = MarkdownRendererPropsSchema;
681
+ exports.ProgressBar = ProgressBar;
682
+ exports.ProgressBarPropsSchema = ProgressBarPropsSchema;
683
+ exports.StatusBadge = StatusBadge;
684
+ exports.StatusBadgePropsSchema = StatusBadgePropsSchema;
685
+ exports.TableView = TableView;
686
+ exports.TableViewPropsSchema = TableViewPropsSchema;
687
+ exports.TokenDisplayOnceModal = TokenDisplayOnceModal;
688
+ exports.TokenDisplayOnceModalPropsSchema = TokenDisplayOnceModalPropsSchema;
689
+ exports.createMosaicResource = createMosaicResource;
690
+ exports.createPostMessageObservable = createPostMessageObservable;
691
+ exports.useStreamingHydration = useStreamingHydration;
692
+ exports.validateConfirmDialogProps = validateConfirmDialogProps;
693
+ exports.validateMarkdownRendererProps = validateProps2;
694
+ exports.validateProps = validateProps;
695
+ exports.validateStatusBadgeProps = validateProps3;
696
+ exports.validateTableViewProps = validateTableViewProps;
697
+ exports.validateTokenDisplayOnceModalProps = validateTokenDisplayOnceModalProps;
698
+ //# sourceMappingURL=index.cjs.map
699
+ //# sourceMappingURL=index.cjs.map