@frontmcp/ui 0.8.0 → 0.9.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/README.md +67 -188
- package/bridge/adapters/claude.adapter.d.ts.map +1 -1
- package/bridge/adapters/gemini.adapter.d.ts.map +1 -1
- package/bridge/index.js +16 -4
- package/components/alert.d.ts +20 -3
- package/components/alert.d.ts.map +1 -1
- package/components/badge.d.ts +16 -2
- package/components/badge.d.ts.map +1 -1
- package/components/card.d.ts +29 -6
- package/components/card.d.ts.map +1 -1
- package/components/form.d.ts +21 -6
- package/components/form.d.ts.map +1 -1
- package/components/index.js +83 -30
- package/esm/bridge/index.mjs +16 -4
- package/esm/components/index.mjs +83 -30
- package/esm/index.mjs +94 -31
- package/esm/package.json +2 -2
- package/esm/renderers/index.mjs +11 -1
- package/esm/universal/index.mjs +2 -7
- package/esm/web-components/index.mjs +52 -22
- package/index.js +94 -31
- package/package.json +2 -2
- package/renderers/index.js +11 -1
- package/renderers/react.adapter.d.ts.map +1 -1
- package/universal/index.js +4 -9
- package/universal/renderers/html.renderer.d.ts +7 -8
- package/universal/renderers/html.renderer.d.ts.map +1 -1
- package/web-components/index.js +52 -22
package/esm/index.mjs
CHANGED
|
@@ -1022,6 +1022,7 @@ var dangerButton = (text, opts) => button(text, { ...opts, variant: "danger" });
|
|
|
1022
1022
|
var linkButton = (text, opts) => button(text, { ...opts, variant: "link" });
|
|
1023
1023
|
|
|
1024
1024
|
// libs/ui/src/components/card.ts
|
|
1025
|
+
import { sanitizeHtmlContent } from "@frontmcp/uipack/runtime";
|
|
1025
1026
|
function getVariantClasses2(variant) {
|
|
1026
1027
|
const variants = {
|
|
1027
1028
|
default: "bg-white border border-border rounded-xl shadow-sm",
|
|
@@ -1056,33 +1057,38 @@ function card(content, options = {}) {
|
|
|
1056
1057
|
id,
|
|
1057
1058
|
data,
|
|
1058
1059
|
clickable = false,
|
|
1059
|
-
href
|
|
1060
|
+
href,
|
|
1061
|
+
sanitize = false
|
|
1060
1062
|
} = options;
|
|
1063
|
+
const safeContent = sanitize ? sanitizeHtmlContent(content) : content;
|
|
1064
|
+
const safeHeaderActions = sanitize && headerActions ? sanitizeHtmlContent(headerActions) : headerActions;
|
|
1065
|
+
const safeFooter = sanitize && footer ? sanitizeHtmlContent(footer) : footer;
|
|
1061
1066
|
const variantClasses = getVariantClasses2(variant);
|
|
1062
1067
|
const sizeClasses = getSizeClasses2(size);
|
|
1063
1068
|
const clickableClasses = clickable ? "cursor-pointer hover:shadow-md transition-shadow" : "";
|
|
1064
|
-
const
|
|
1069
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1070
|
+
const allClasses = [variantClasses, sizeClasses, clickableClasses, safeClassName].filter(Boolean).join(" ");
|
|
1065
1071
|
const dataAttrs = buildDataAttrs(data);
|
|
1066
1072
|
const idAttr = id ? `id="${escapeHtml2(id)}"` : "";
|
|
1067
|
-
const hasHeader = title || subtitle ||
|
|
1073
|
+
const hasHeader = title || subtitle || safeHeaderActions;
|
|
1068
1074
|
const headerHtml = hasHeader ? `<div class="flex items-start justify-between mb-4">
|
|
1069
1075
|
<div>
|
|
1070
1076
|
${title ? `<h3 class="text-lg font-semibold text-text-primary">${escapeHtml2(title)}</h3>` : ""}
|
|
1071
1077
|
${subtitle ? `<p class="text-sm text-text-secondary mt-1">${escapeHtml2(subtitle)}</p>` : ""}
|
|
1072
1078
|
</div>
|
|
1073
|
-
${
|
|
1079
|
+
${safeHeaderActions ? `<div class="flex items-center gap-2">${safeHeaderActions}</div>` : ""}
|
|
1074
1080
|
</div>` : "";
|
|
1075
|
-
const footerHtml =
|
|
1081
|
+
const footerHtml = safeFooter ? `<div class="mt-4 pt-4 border-t border-divider">${safeFooter}</div>` : "";
|
|
1076
1082
|
if (href) {
|
|
1077
1083
|
return `<a href="${escapeHtml2(href)}" class="${allClasses}" ${idAttr} ${dataAttrs}>
|
|
1078
1084
|
${headerHtml}
|
|
1079
|
-
${
|
|
1085
|
+
${safeContent}
|
|
1080
1086
|
${footerHtml}
|
|
1081
1087
|
</a>`;
|
|
1082
1088
|
}
|
|
1083
1089
|
return `<div class="${allClasses}" ${idAttr} ${dataAttrs}>
|
|
1084
1090
|
${headerHtml}
|
|
1085
|
-
${
|
|
1091
|
+
${safeContent}
|
|
1086
1092
|
${footerHtml}
|
|
1087
1093
|
</div>`;
|
|
1088
1094
|
}
|
|
@@ -1090,12 +1096,14 @@ function cardGroup(cards, options = {}) {
|
|
|
1090
1096
|
const { direction = "vertical", gap = "md", className = "" } = options;
|
|
1091
1097
|
const gapClasses = { sm: "gap-2", md: "gap-4", lg: "gap-6" };
|
|
1092
1098
|
const directionClasses = direction === "horizontal" ? "flex flex-row flex-wrap" : "flex flex-col";
|
|
1093
|
-
|
|
1099
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1100
|
+
return `<div class="${directionClasses} ${gapClasses[gap]} ${safeClassName}">
|
|
1094
1101
|
${cards.join("\n")}
|
|
1095
1102
|
</div>`;
|
|
1096
1103
|
}
|
|
1097
1104
|
|
|
1098
1105
|
// libs/ui/src/components/form.ts
|
|
1106
|
+
import { sanitizeHtmlContent as sanitizeHtmlContent2 } from "@frontmcp/uipack/runtime";
|
|
1099
1107
|
function getInputSizeClasses(size) {
|
|
1100
1108
|
const sizes = {
|
|
1101
1109
|
sm: "px-3 py-1.5 text-sm",
|
|
@@ -1140,11 +1148,15 @@ function input(options) {
|
|
|
1140
1148
|
className = "",
|
|
1141
1149
|
data,
|
|
1142
1150
|
iconBefore,
|
|
1143
|
-
iconAfter
|
|
1151
|
+
iconAfter,
|
|
1152
|
+
sanitize = false
|
|
1144
1153
|
} = options;
|
|
1154
|
+
const safeIconBefore = sanitize && iconBefore ? sanitizeHtmlContent2(iconBefore) : iconBefore;
|
|
1155
|
+
const safeIconAfter = sanitize && iconAfter ? sanitizeHtmlContent2(iconAfter) : iconAfter;
|
|
1145
1156
|
const sizeClasses = getInputSizeClasses(size);
|
|
1146
1157
|
const stateClasses = getInputStateClasses(state);
|
|
1147
|
-
const hasIcon =
|
|
1158
|
+
const hasIcon = safeIconBefore || safeIconAfter;
|
|
1159
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1148
1160
|
const baseClasses = [
|
|
1149
1161
|
"w-full rounded-lg border bg-white",
|
|
1150
1162
|
"transition-colors duration-200",
|
|
@@ -1152,8 +1164,8 @@ function input(options) {
|
|
|
1152
1164
|
disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
|
|
1153
1165
|
sizeClasses,
|
|
1154
1166
|
stateClasses,
|
|
1155
|
-
hasIcon ? (
|
|
1156
|
-
|
|
1167
|
+
hasIcon ? (safeIconBefore ? "pl-10" : "") + (safeIconAfter ? " pr-10" : "") : "",
|
|
1168
|
+
safeClassName
|
|
1157
1169
|
].filter(Boolean).join(" ");
|
|
1158
1170
|
const dataAttrs = buildDataAttrs2(data);
|
|
1159
1171
|
const inputAttrs = [
|
|
@@ -1178,8 +1190,8 @@ function input(options) {
|
|
|
1178
1190
|
</label>` : "";
|
|
1179
1191
|
const helperHtml = helper && !error ? `<p class="mt-1.5 text-sm text-text-secondary">${escapeHtml2(helper)}</p>` : "";
|
|
1180
1192
|
const errorHtml = error ? `<p class="mt-1.5 text-sm text-danger">${escapeHtml2(error)}</p>` : "";
|
|
1181
|
-
const iconBeforeHtml =
|
|
1182
|
-
const iconAfterHtml =
|
|
1193
|
+
const iconBeforeHtml = safeIconBefore ? `<span class="absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconBefore}</span>` : "";
|
|
1194
|
+
const iconAfterHtml = safeIconAfter ? `<span class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconAfter}</span>` : "";
|
|
1183
1195
|
const inputHtml = hasIcon ? `<div class="relative">
|
|
1184
1196
|
${iconBeforeHtml}
|
|
1185
1197
|
<input ${inputAttrs}>
|
|
@@ -1211,6 +1223,7 @@ function select(options) {
|
|
|
1211
1223
|
} = options;
|
|
1212
1224
|
const sizeClasses = getInputSizeClasses(size);
|
|
1213
1225
|
const stateClasses = getInputStateClasses(state);
|
|
1226
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1214
1227
|
const baseClasses = [
|
|
1215
1228
|
"w-full rounded-lg border bg-white",
|
|
1216
1229
|
"transition-colors duration-200",
|
|
@@ -1218,7 +1231,7 @@ function select(options) {
|
|
|
1218
1231
|
disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
|
|
1219
1232
|
sizeClasses,
|
|
1220
1233
|
stateClasses,
|
|
1221
|
-
|
|
1234
|
+
safeClassName
|
|
1222
1235
|
].filter(Boolean).join(" ");
|
|
1223
1236
|
const dataAttrs = buildDataAttrs2(data);
|
|
1224
1237
|
const optionsHtml = selectOptions.map((opt) => {
|
|
@@ -1275,6 +1288,7 @@ function textarea(options) {
|
|
|
1275
1288
|
horizontal: "resize-x",
|
|
1276
1289
|
both: "resize"
|
|
1277
1290
|
};
|
|
1291
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1278
1292
|
const baseClasses = [
|
|
1279
1293
|
"w-full rounded-lg border bg-white",
|
|
1280
1294
|
"transition-colors duration-200",
|
|
@@ -1283,7 +1297,7 @@ function textarea(options) {
|
|
|
1283
1297
|
sizeClasses,
|
|
1284
1298
|
stateClasses,
|
|
1285
1299
|
resizeClasses[resize],
|
|
1286
|
-
|
|
1300
|
+
safeClassName
|
|
1287
1301
|
].filter(Boolean).join(" ");
|
|
1288
1302
|
const dataAttrs = buildDataAttrs2(data);
|
|
1289
1303
|
const labelHtml = label ? `<label for="${escapeHtml2(id)}" class="block text-sm font-medium text-text-primary mb-1.5">
|
|
@@ -1327,7 +1341,8 @@ function checkbox(options) {
|
|
|
1327
1341
|
].join(" ");
|
|
1328
1342
|
const helperHtml = helper && !error ? `<p class="text-sm text-text-secondary">${escapeHtml2(helper)}</p>` : "";
|
|
1329
1343
|
const errorHtml = error ? `<p class="text-sm text-danger">${escapeHtml2(error)}</p>` : "";
|
|
1330
|
-
|
|
1344
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1345
|
+
return `<div class="form-field ${safeClassName}">
|
|
1331
1346
|
<label class="flex items-start gap-3 ${disabled ? "cursor-not-allowed" : "cursor-pointer"}">
|
|
1332
1347
|
<input
|
|
1333
1348
|
type="checkbox"
|
|
@@ -1371,7 +1386,8 @@ function radioGroup(options) {
|
|
|
1371
1386
|
const labelHtml = label ? `<label class="block text-sm font-medium text-text-primary mb-2">${escapeHtml2(label)}</label>` : "";
|
|
1372
1387
|
const helperHtml = helper && !error ? `<p class="mt-1.5 text-sm text-text-secondary">${escapeHtml2(helper)}</p>` : "";
|
|
1373
1388
|
const errorHtml = error ? `<p class="mt-1.5 text-sm text-danger">${escapeHtml2(error)}</p>` : "";
|
|
1374
|
-
|
|
1389
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1390
|
+
return `<div class="form-field ${safeClassName}" role="radiogroup">
|
|
1375
1391
|
${labelHtml}
|
|
1376
1392
|
<div class="${directionClasses}">
|
|
1377
1393
|
${radiosHtml}
|
|
@@ -1393,10 +1409,26 @@ function form(content, options = {}) {
|
|
|
1393
1409
|
].filter(Boolean).join(" ");
|
|
1394
1410
|
return `<form ${attrs}>${content}</form>`;
|
|
1395
1411
|
}
|
|
1412
|
+
var GRID_COLS_MAP = {
|
|
1413
|
+
1: "grid-cols-1",
|
|
1414
|
+
2: "grid-cols-2",
|
|
1415
|
+
3: "grid-cols-3",
|
|
1416
|
+
4: "grid-cols-4",
|
|
1417
|
+
5: "grid-cols-5",
|
|
1418
|
+
6: "grid-cols-6",
|
|
1419
|
+
7: "grid-cols-7",
|
|
1420
|
+
8: "grid-cols-8",
|
|
1421
|
+
9: "grid-cols-9",
|
|
1422
|
+
10: "grid-cols-10",
|
|
1423
|
+
11: "grid-cols-11",
|
|
1424
|
+
12: "grid-cols-12"
|
|
1425
|
+
};
|
|
1396
1426
|
function formRow(fields, options = {}) {
|
|
1397
1427
|
const { gap = "md", className = "" } = options;
|
|
1398
1428
|
const gapClasses = { sm: "gap-2", md: "gap-4", lg: "gap-6" };
|
|
1399
|
-
|
|
1429
|
+
const gridCols = GRID_COLS_MAP[fields.length] ?? "grid-cols-1";
|
|
1430
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1431
|
+
return `<div class="grid ${gridCols} ${gapClasses[gap]} ${safeClassName}">
|
|
1400
1432
|
${fields.join("\n")}
|
|
1401
1433
|
</div>`;
|
|
1402
1434
|
}
|
|
@@ -1406,7 +1438,8 @@ function formSection(content, options = {}) {
|
|
|
1406
1438
|
<h3 class="text-lg font-semibold text-text-primary">${escapeHtml2(title)}</h3>
|
|
1407
1439
|
${description ? `<p class="text-sm text-text-secondary mt-1">${escapeHtml2(description)}</p>` : ""}
|
|
1408
1440
|
</div>` : "";
|
|
1409
|
-
|
|
1441
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1442
|
+
return `<div class="form-section ${safeClassName}">
|
|
1410
1443
|
${headerHtml}
|
|
1411
1444
|
<div class="space-y-4">
|
|
1412
1445
|
${content}
|
|
@@ -1421,7 +1454,8 @@ function formActions(buttons, options = {}) {
|
|
|
1421
1454
|
right: "justify-end",
|
|
1422
1455
|
between: "justify-between"
|
|
1423
1456
|
};
|
|
1424
|
-
|
|
1457
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1458
|
+
return `<div class="flex items-center gap-3 pt-4 ${alignClasses[align]} ${safeClassName}">
|
|
1425
1459
|
${buttons.join("\n")}
|
|
1426
1460
|
</div>`;
|
|
1427
1461
|
}
|
|
@@ -1433,6 +1467,7 @@ function csrfInput(token) {
|
|
|
1433
1467
|
}
|
|
1434
1468
|
|
|
1435
1469
|
// libs/ui/src/components/badge.ts
|
|
1470
|
+
import { sanitizeHtmlContent as sanitizeHtmlContent3 } from "@frontmcp/uipack/runtime";
|
|
1436
1471
|
function getVariantClasses3(variant) {
|
|
1437
1472
|
const variants = {
|
|
1438
1473
|
default: "bg-gray-100 text-gray-800",
|
|
@@ -1470,8 +1505,11 @@ function badge(text, options = {}) {
|
|
|
1470
1505
|
icon,
|
|
1471
1506
|
dot = false,
|
|
1472
1507
|
className = "",
|
|
1473
|
-
removable = false
|
|
1508
|
+
removable = false,
|
|
1509
|
+
sanitize = false
|
|
1474
1510
|
} = options;
|
|
1511
|
+
const safeIcon = sanitize && icon ? sanitizeHtmlContent3(icon) : icon;
|
|
1512
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1475
1513
|
if (dot) {
|
|
1476
1514
|
const dotVariants = {
|
|
1477
1515
|
default: "bg-gray-400",
|
|
@@ -1483,7 +1521,7 @@ function badge(text, options = {}) {
|
|
|
1483
1521
|
info: "bg-blue-500",
|
|
1484
1522
|
outline: "border border-current"
|
|
1485
1523
|
};
|
|
1486
|
-
const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant],
|
|
1524
|
+
const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant], safeClassName].filter(Boolean).join(" ");
|
|
1487
1525
|
return `<span class="${dotClasses}" aria-label="${escapeHtml2(text)}" title="${escapeHtml2(text)}"></span>`;
|
|
1488
1526
|
}
|
|
1489
1527
|
const variantClasses = getVariantClasses3(variant);
|
|
@@ -1493,9 +1531,9 @@ function badge(text, options = {}) {
|
|
|
1493
1531
|
pill ? "rounded-full" : "rounded-md",
|
|
1494
1532
|
variantClasses,
|
|
1495
1533
|
sizeClasses,
|
|
1496
|
-
|
|
1534
|
+
safeClassName
|
|
1497
1535
|
].filter(Boolean).join(" ");
|
|
1498
|
-
const iconHtml =
|
|
1536
|
+
const iconHtml = safeIcon ? `<span class="mr-1">${safeIcon}</span>` : "";
|
|
1499
1537
|
const removeHtml = removable ? `<button
|
|
1500
1538
|
type="button"
|
|
1501
1539
|
class="ml-1.5 -mr-1 hover:opacity-70 transition-opacity"
|
|
@@ -1513,7 +1551,8 @@ function badge(text, options = {}) {
|
|
|
1513
1551
|
function badgeGroup(badges, options = {}) {
|
|
1514
1552
|
const { gap = "sm", className = "" } = options;
|
|
1515
1553
|
const gapClasses = { sm: "gap-1", md: "gap-2", lg: "gap-3" };
|
|
1516
|
-
|
|
1554
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1555
|
+
return `<div class="inline-flex flex-wrap ${gapClasses[gap]} ${safeClassName}">
|
|
1517
1556
|
${badges.join("\n")}
|
|
1518
1557
|
</div>`;
|
|
1519
1558
|
}
|
|
@@ -1529,6 +1568,7 @@ var busyDot = (label = "Busy") => badge(label, { variant: "danger", dot: true })
|
|
|
1529
1568
|
var awayDot = (label = "Away") => badge(label, { variant: "warning", dot: true });
|
|
1530
1569
|
|
|
1531
1570
|
// libs/ui/src/components/alert.ts
|
|
1571
|
+
import { sanitizeHtmlContent as sanitizeHtmlContent4 } from "@frontmcp/uipack/runtime";
|
|
1532
1572
|
var alertIcons = {
|
|
1533
1573
|
info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1534
1574
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
@@ -1572,11 +1612,24 @@ function getVariantClasses4(variant) {
|
|
|
1572
1612
|
return variants[variant];
|
|
1573
1613
|
}
|
|
1574
1614
|
function alert(message, options = {}) {
|
|
1575
|
-
const {
|
|
1615
|
+
const {
|
|
1616
|
+
variant = "info",
|
|
1617
|
+
title,
|
|
1618
|
+
showIcon = true,
|
|
1619
|
+
icon,
|
|
1620
|
+
dismissible = false,
|
|
1621
|
+
className = "",
|
|
1622
|
+
id,
|
|
1623
|
+
actions,
|
|
1624
|
+
sanitize = false
|
|
1625
|
+
} = options;
|
|
1626
|
+
const safeIcon = sanitize && icon ? sanitizeHtmlContent4(icon) : icon;
|
|
1627
|
+
const safeActions = sanitize && actions ? sanitizeHtmlContent4(actions) : actions;
|
|
1576
1628
|
const variantClasses = getVariantClasses4(variant);
|
|
1577
|
-
const
|
|
1629
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1630
|
+
const baseClasses = ["rounded-lg border p-4", variantClasses.container, safeClassName].filter(Boolean).join(" ");
|
|
1578
1631
|
const iconHtml = showIcon ? `<div class="flex-shrink-0 ${variantClasses.icon}">
|
|
1579
|
-
${
|
|
1632
|
+
${safeIcon || alertIcons[variant]}
|
|
1580
1633
|
</div>` : "";
|
|
1581
1634
|
const titleHtml = title ? `<h3 class="font-semibold">${escapeHtml2(title)}</h3>` : "";
|
|
1582
1635
|
const dismissHtml = dismissible ? `<button
|
|
@@ -1589,7 +1642,7 @@ function alert(message, options = {}) {
|
|
|
1589
1642
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
1590
1643
|
</svg>
|
|
1591
1644
|
</button>` : "";
|
|
1592
|
-
const actionsHtml =
|
|
1645
|
+
const actionsHtml = safeActions ? `<div class="mt-3">${safeActions}</div>` : "";
|
|
1593
1646
|
const idAttr = id ? `id="${escapeHtml2(id)}"` : "";
|
|
1594
1647
|
return `<div class="alert ${baseClasses}" role="alert" ${idAttr}>
|
|
1595
1648
|
<div class="flex gap-3">
|
|
@@ -5088,7 +5141,17 @@ var ReactRendererAdapter = class {
|
|
|
5088
5141
|
return true;
|
|
5089
5142
|
}
|
|
5090
5143
|
if (typeof content === "string") {
|
|
5091
|
-
|
|
5144
|
+
if (content.includes("React.createElement") || content.includes("jsx(") || content.includes("jsxs(")) {
|
|
5145
|
+
return true;
|
|
5146
|
+
}
|
|
5147
|
+
const funcIndex = content.indexOf("function");
|
|
5148
|
+
if (funcIndex !== -1) {
|
|
5149
|
+
const afterFunc = content.slice(funcIndex, Math.min(funcIndex + 2e3, content.length));
|
|
5150
|
+
if (afterFunc.includes("return") && afterFunc.includes("<")) {
|
|
5151
|
+
return true;
|
|
5152
|
+
}
|
|
5153
|
+
}
|
|
5154
|
+
return false;
|
|
5092
5155
|
}
|
|
5093
5156
|
return false;
|
|
5094
5157
|
}
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "FrontMCP UI - React components and hooks for MCP applications",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"react": "^18.0.0 || ^19.0.0",
|
|
66
66
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
67
|
-
"@frontmcp/uipack": "0.
|
|
67
|
+
"@frontmcp/uipack": "0.9.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/react": "^19.0.0",
|
package/esm/renderers/index.mjs
CHANGED
|
@@ -206,7 +206,17 @@ var ReactRendererAdapter = class {
|
|
|
206
206
|
return true;
|
|
207
207
|
}
|
|
208
208
|
if (typeof content === "string") {
|
|
209
|
-
|
|
209
|
+
if (content.includes("React.createElement") || content.includes("jsx(") || content.includes("jsxs(")) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
const funcIndex = content.indexOf("function");
|
|
213
|
+
if (funcIndex !== -1) {
|
|
214
|
+
const afterFunc = content.slice(funcIndex, Math.min(funcIndex + 2e3, content.length));
|
|
215
|
+
if (afterFunc.includes("return") && afterFunc.includes("<")) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
210
220
|
}
|
|
211
221
|
return false;
|
|
212
222
|
}
|
package/esm/universal/index.mjs
CHANGED
|
@@ -202,6 +202,8 @@ function withFrontMCP(Component) {
|
|
|
202
202
|
|
|
203
203
|
// libs/ui/src/universal/renderers/html.renderer.ts
|
|
204
204
|
import React2 from "react";
|
|
205
|
+
import { sanitizeHtmlContent } from "@frontmcp/uipack/runtime";
|
|
206
|
+
var sanitizeHtml = sanitizeHtmlContent;
|
|
205
207
|
var htmlRenderer = {
|
|
206
208
|
type: "html",
|
|
207
209
|
priority: 0,
|
|
@@ -220,13 +222,6 @@ var htmlRenderer = {
|
|
|
220
222
|
});
|
|
221
223
|
}
|
|
222
224
|
};
|
|
223
|
-
function sanitizeHtml(html) {
|
|
224
|
-
let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
|
|
225
|
-
sanitized = sanitized.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, "");
|
|
226
|
-
sanitized = sanitized.replace(/\s+on\w+\s*=\s*[^\s>]*/gi, "");
|
|
227
|
-
sanitized = sanitized.replace(/href\s*=\s*["']javascript:[^"']*["']/gi, 'href="#"');
|
|
228
|
-
return sanitized;
|
|
229
|
-
}
|
|
230
225
|
var safeHtmlRenderer = {
|
|
231
226
|
type: "html",
|
|
232
227
|
priority: 0,
|
|
@@ -618,6 +618,7 @@ function registerFmcpButton() {
|
|
|
618
618
|
}
|
|
619
619
|
|
|
620
620
|
// libs/ui/src/components/card.ts
|
|
621
|
+
import { sanitizeHtmlContent } from "@frontmcp/uipack/runtime";
|
|
621
622
|
function getVariantClasses2(variant) {
|
|
622
623
|
const variants = {
|
|
623
624
|
default: "bg-white border border-border rounded-xl shadow-sm",
|
|
@@ -652,33 +653,38 @@ function card(content, options = {}) {
|
|
|
652
653
|
id,
|
|
653
654
|
data,
|
|
654
655
|
clickable = false,
|
|
655
|
-
href
|
|
656
|
+
href,
|
|
657
|
+
sanitize = false
|
|
656
658
|
} = options;
|
|
659
|
+
const safeContent = sanitize ? sanitizeHtmlContent(content) : content;
|
|
660
|
+
const safeHeaderActions = sanitize && headerActions ? sanitizeHtmlContent(headerActions) : headerActions;
|
|
661
|
+
const safeFooter = sanitize && footer ? sanitizeHtmlContent(footer) : footer;
|
|
657
662
|
const variantClasses = getVariantClasses2(variant);
|
|
658
663
|
const sizeClasses = getSizeClasses2(size);
|
|
659
664
|
const clickableClasses = clickable ? "cursor-pointer hover:shadow-md transition-shadow" : "";
|
|
660
|
-
const
|
|
665
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
666
|
+
const allClasses = [variantClasses, sizeClasses, clickableClasses, safeClassName].filter(Boolean).join(" ");
|
|
661
667
|
const dataAttrs = buildDataAttrs(data);
|
|
662
668
|
const idAttr = id ? `id="${escapeHtml2(id)}"` : "";
|
|
663
|
-
const hasHeader = title || subtitle ||
|
|
669
|
+
const hasHeader = title || subtitle || safeHeaderActions;
|
|
664
670
|
const headerHtml = hasHeader ? `<div class="flex items-start justify-between mb-4">
|
|
665
671
|
<div>
|
|
666
672
|
${title ? `<h3 class="text-lg font-semibold text-text-primary">${escapeHtml2(title)}</h3>` : ""}
|
|
667
673
|
${subtitle ? `<p class="text-sm text-text-secondary mt-1">${escapeHtml2(subtitle)}</p>` : ""}
|
|
668
674
|
</div>
|
|
669
|
-
${
|
|
675
|
+
${safeHeaderActions ? `<div class="flex items-center gap-2">${safeHeaderActions}</div>` : ""}
|
|
670
676
|
</div>` : "";
|
|
671
|
-
const footerHtml =
|
|
677
|
+
const footerHtml = safeFooter ? `<div class="mt-4 pt-4 border-t border-divider">${safeFooter}</div>` : "";
|
|
672
678
|
if (href) {
|
|
673
679
|
return `<a href="${escapeHtml2(href)}" class="${allClasses}" ${idAttr} ${dataAttrs}>
|
|
674
680
|
${headerHtml}
|
|
675
|
-
${
|
|
681
|
+
${safeContent}
|
|
676
682
|
${footerHtml}
|
|
677
683
|
</a>`;
|
|
678
684
|
}
|
|
679
685
|
return `<div class="${allClasses}" ${idAttr} ${dataAttrs}>
|
|
680
686
|
${headerHtml}
|
|
681
|
-
${
|
|
687
|
+
${safeContent}
|
|
682
688
|
${footerHtml}
|
|
683
689
|
</div>`;
|
|
684
690
|
}
|
|
@@ -801,6 +807,7 @@ function registerFmcpCard() {
|
|
|
801
807
|
}
|
|
802
808
|
|
|
803
809
|
// libs/ui/src/components/alert.ts
|
|
810
|
+
import { sanitizeHtmlContent as sanitizeHtmlContent2 } from "@frontmcp/uipack/runtime";
|
|
804
811
|
var alertIcons = {
|
|
805
812
|
info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
806
813
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
@@ -844,11 +851,24 @@ function getVariantClasses3(variant) {
|
|
|
844
851
|
return variants[variant];
|
|
845
852
|
}
|
|
846
853
|
function alert(message, options = {}) {
|
|
847
|
-
const {
|
|
854
|
+
const {
|
|
855
|
+
variant = "info",
|
|
856
|
+
title,
|
|
857
|
+
showIcon = true,
|
|
858
|
+
icon,
|
|
859
|
+
dismissible = false,
|
|
860
|
+
className = "",
|
|
861
|
+
id,
|
|
862
|
+
actions,
|
|
863
|
+
sanitize = false
|
|
864
|
+
} = options;
|
|
865
|
+
const safeIcon = sanitize && icon ? sanitizeHtmlContent2(icon) : icon;
|
|
866
|
+
const safeActions = sanitize && actions ? sanitizeHtmlContent2(actions) : actions;
|
|
848
867
|
const variantClasses = getVariantClasses3(variant);
|
|
849
|
-
const
|
|
868
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
869
|
+
const baseClasses = ["rounded-lg border p-4", variantClasses.container, safeClassName].filter(Boolean).join(" ");
|
|
850
870
|
const iconHtml = showIcon ? `<div class="flex-shrink-0 ${variantClasses.icon}">
|
|
851
|
-
${
|
|
871
|
+
${safeIcon || alertIcons[variant]}
|
|
852
872
|
</div>` : "";
|
|
853
873
|
const titleHtml = title ? `<h3 class="font-semibold">${escapeHtml2(title)}</h3>` : "";
|
|
854
874
|
const dismissHtml = dismissible ? `<button
|
|
@@ -861,7 +881,7 @@ function alert(message, options = {}) {
|
|
|
861
881
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
862
882
|
</svg>
|
|
863
883
|
</button>` : "";
|
|
864
|
-
const actionsHtml =
|
|
884
|
+
const actionsHtml = safeActions ? `<div class="mt-3">${safeActions}</div>` : "";
|
|
865
885
|
const idAttr = id ? `id="${escapeHtml2(id)}"` : "";
|
|
866
886
|
return `<div class="alert ${baseClasses}" role="alert" ${idAttr}>
|
|
867
887
|
<div class="flex gap-3">
|
|
@@ -978,6 +998,7 @@ function registerFmcpAlert() {
|
|
|
978
998
|
}
|
|
979
999
|
|
|
980
1000
|
// libs/ui/src/components/badge.ts
|
|
1001
|
+
import { sanitizeHtmlContent as sanitizeHtmlContent3 } from "@frontmcp/uipack/runtime";
|
|
981
1002
|
function getVariantClasses4(variant) {
|
|
982
1003
|
const variants = {
|
|
983
1004
|
default: "bg-gray-100 text-gray-800",
|
|
@@ -1015,8 +1036,11 @@ function badge(text, options = {}) {
|
|
|
1015
1036
|
icon,
|
|
1016
1037
|
dot = false,
|
|
1017
1038
|
className = "",
|
|
1018
|
-
removable = false
|
|
1039
|
+
removable = false,
|
|
1040
|
+
sanitize = false
|
|
1019
1041
|
} = options;
|
|
1042
|
+
const safeIcon = sanitize && icon ? sanitizeHtmlContent3(icon) : icon;
|
|
1043
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1020
1044
|
if (dot) {
|
|
1021
1045
|
const dotVariants = {
|
|
1022
1046
|
default: "bg-gray-400",
|
|
@@ -1028,7 +1052,7 @@ function badge(text, options = {}) {
|
|
|
1028
1052
|
info: "bg-blue-500",
|
|
1029
1053
|
outline: "border border-current"
|
|
1030
1054
|
};
|
|
1031
|
-
const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant],
|
|
1055
|
+
const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant], safeClassName].filter(Boolean).join(" ");
|
|
1032
1056
|
return `<span class="${dotClasses}" aria-label="${escapeHtml2(text)}" title="${escapeHtml2(text)}"></span>`;
|
|
1033
1057
|
}
|
|
1034
1058
|
const variantClasses = getVariantClasses4(variant);
|
|
@@ -1038,9 +1062,9 @@ function badge(text, options = {}) {
|
|
|
1038
1062
|
pill ? "rounded-full" : "rounded-md",
|
|
1039
1063
|
variantClasses,
|
|
1040
1064
|
sizeClasses,
|
|
1041
|
-
|
|
1065
|
+
safeClassName
|
|
1042
1066
|
].filter(Boolean).join(" ");
|
|
1043
|
-
const iconHtml =
|
|
1067
|
+
const iconHtml = safeIcon ? `<span class="mr-1">${safeIcon}</span>` : "";
|
|
1044
1068
|
const removeHtml = removable ? `<button
|
|
1045
1069
|
type="button"
|
|
1046
1070
|
class="ml-1.5 -mr-1 hover:opacity-70 transition-opacity"
|
|
@@ -1159,6 +1183,7 @@ function registerFmcpBadge() {
|
|
|
1159
1183
|
}
|
|
1160
1184
|
|
|
1161
1185
|
// libs/ui/src/components/form.ts
|
|
1186
|
+
import { sanitizeHtmlContent as sanitizeHtmlContent4 } from "@frontmcp/uipack/runtime";
|
|
1162
1187
|
function getInputSizeClasses(size) {
|
|
1163
1188
|
const sizes = {
|
|
1164
1189
|
sm: "px-3 py-1.5 text-sm",
|
|
@@ -1203,11 +1228,15 @@ function input(options) {
|
|
|
1203
1228
|
className = "",
|
|
1204
1229
|
data,
|
|
1205
1230
|
iconBefore,
|
|
1206
|
-
iconAfter
|
|
1231
|
+
iconAfter,
|
|
1232
|
+
sanitize = false
|
|
1207
1233
|
} = options;
|
|
1234
|
+
const safeIconBefore = sanitize && iconBefore ? sanitizeHtmlContent4(iconBefore) : iconBefore;
|
|
1235
|
+
const safeIconAfter = sanitize && iconAfter ? sanitizeHtmlContent4(iconAfter) : iconAfter;
|
|
1208
1236
|
const sizeClasses = getInputSizeClasses(size);
|
|
1209
1237
|
const stateClasses = getInputStateClasses(state);
|
|
1210
|
-
const hasIcon =
|
|
1238
|
+
const hasIcon = safeIconBefore || safeIconAfter;
|
|
1239
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1211
1240
|
const baseClasses = [
|
|
1212
1241
|
"w-full rounded-lg border bg-white",
|
|
1213
1242
|
"transition-colors duration-200",
|
|
@@ -1215,8 +1244,8 @@ function input(options) {
|
|
|
1215
1244
|
disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
|
|
1216
1245
|
sizeClasses,
|
|
1217
1246
|
stateClasses,
|
|
1218
|
-
hasIcon ? (
|
|
1219
|
-
|
|
1247
|
+
hasIcon ? (safeIconBefore ? "pl-10" : "") + (safeIconAfter ? " pr-10" : "") : "",
|
|
1248
|
+
safeClassName
|
|
1220
1249
|
].filter(Boolean).join(" ");
|
|
1221
1250
|
const dataAttrs = buildDataAttrs2(data);
|
|
1222
1251
|
const inputAttrs = [
|
|
@@ -1241,8 +1270,8 @@ function input(options) {
|
|
|
1241
1270
|
</label>` : "";
|
|
1242
1271
|
const helperHtml = helper && !error ? `<p class="mt-1.5 text-sm text-text-secondary">${escapeHtml2(helper)}</p>` : "";
|
|
1243
1272
|
const errorHtml = error ? `<p class="mt-1.5 text-sm text-danger">${escapeHtml2(error)}</p>` : "";
|
|
1244
|
-
const iconBeforeHtml =
|
|
1245
|
-
const iconAfterHtml =
|
|
1273
|
+
const iconBeforeHtml = safeIconBefore ? `<span class="absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconBefore}</span>` : "";
|
|
1274
|
+
const iconAfterHtml = safeIconAfter ? `<span class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconAfter}</span>` : "";
|
|
1246
1275
|
const inputHtml = hasIcon ? `<div class="relative">
|
|
1247
1276
|
${iconBeforeHtml}
|
|
1248
1277
|
<input ${inputAttrs}>
|
|
@@ -1274,6 +1303,7 @@ function select(options) {
|
|
|
1274
1303
|
} = options;
|
|
1275
1304
|
const sizeClasses = getInputSizeClasses(size);
|
|
1276
1305
|
const stateClasses = getInputStateClasses(state);
|
|
1306
|
+
const safeClassName = className ? escapeHtml2(className) : "";
|
|
1277
1307
|
const baseClasses = [
|
|
1278
1308
|
"w-full rounded-lg border bg-white",
|
|
1279
1309
|
"transition-colors duration-200",
|
|
@@ -1281,7 +1311,7 @@ function select(options) {
|
|
|
1281
1311
|
disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
|
|
1282
1312
|
sizeClasses,
|
|
1283
1313
|
stateClasses,
|
|
1284
|
-
|
|
1314
|
+
safeClassName
|
|
1285
1315
|
].filter(Boolean).join(" ");
|
|
1286
1316
|
const dataAttrs = buildDataAttrs2(data);
|
|
1287
1317
|
const optionsHtml = selectOptions.map((opt) => {
|