@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/index.js CHANGED
@@ -1219,6 +1219,7 @@ var dangerButton = (text, opts) => button(text, { ...opts, variant: "danger" });
1219
1219
  var linkButton = (text, opts) => button(text, { ...opts, variant: "link" });
1220
1220
 
1221
1221
  // libs/ui/src/components/card.ts
1222
+ var import_runtime = require("@frontmcp/uipack/runtime");
1222
1223
  function getVariantClasses2(variant) {
1223
1224
  const variants = {
1224
1225
  default: "bg-white border border-border rounded-xl shadow-sm",
@@ -1253,33 +1254,38 @@ function card(content, options = {}) {
1253
1254
  id,
1254
1255
  data,
1255
1256
  clickable = false,
1256
- href
1257
+ href,
1258
+ sanitize = false
1257
1259
  } = options;
1260
+ const safeContent = sanitize ? (0, import_runtime.sanitizeHtmlContent)(content) : content;
1261
+ const safeHeaderActions = sanitize && headerActions ? (0, import_runtime.sanitizeHtmlContent)(headerActions) : headerActions;
1262
+ const safeFooter = sanitize && footer ? (0, import_runtime.sanitizeHtmlContent)(footer) : footer;
1258
1263
  const variantClasses = getVariantClasses2(variant);
1259
1264
  const sizeClasses = getSizeClasses2(size);
1260
1265
  const clickableClasses = clickable ? "cursor-pointer hover:shadow-md transition-shadow" : "";
1261
- const allClasses = [variantClasses, sizeClasses, clickableClasses, className].filter(Boolean).join(" ");
1266
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1267
+ const allClasses = [variantClasses, sizeClasses, clickableClasses, safeClassName].filter(Boolean).join(" ");
1262
1268
  const dataAttrs = buildDataAttrs(data);
1263
1269
  const idAttr = id ? `id="${(0, import_utils2.escapeHtml)(id)}"` : "";
1264
- const hasHeader = title || subtitle || headerActions;
1270
+ const hasHeader = title || subtitle || safeHeaderActions;
1265
1271
  const headerHtml = hasHeader ? `<div class="flex items-start justify-between mb-4">
1266
1272
  <div>
1267
1273
  ${title ? `<h3 class="text-lg font-semibold text-text-primary">${(0, import_utils2.escapeHtml)(title)}</h3>` : ""}
1268
1274
  ${subtitle ? `<p class="text-sm text-text-secondary mt-1">${(0, import_utils2.escapeHtml)(subtitle)}</p>` : ""}
1269
1275
  </div>
1270
- ${headerActions ? `<div class="flex items-center gap-2">${headerActions}</div>` : ""}
1276
+ ${safeHeaderActions ? `<div class="flex items-center gap-2">${safeHeaderActions}</div>` : ""}
1271
1277
  </div>` : "";
1272
- const footerHtml = footer ? `<div class="mt-4 pt-4 border-t border-divider">${footer}</div>` : "";
1278
+ const footerHtml = safeFooter ? `<div class="mt-4 pt-4 border-t border-divider">${safeFooter}</div>` : "";
1273
1279
  if (href) {
1274
1280
  return `<a href="${(0, import_utils2.escapeHtml)(href)}" class="${allClasses}" ${idAttr} ${dataAttrs}>
1275
1281
  ${headerHtml}
1276
- ${content}
1282
+ ${safeContent}
1277
1283
  ${footerHtml}
1278
1284
  </a>`;
1279
1285
  }
1280
1286
  return `<div class="${allClasses}" ${idAttr} ${dataAttrs}>
1281
1287
  ${headerHtml}
1282
- ${content}
1288
+ ${safeContent}
1283
1289
  ${footerHtml}
1284
1290
  </div>`;
1285
1291
  }
@@ -1287,12 +1293,14 @@ function cardGroup(cards, options = {}) {
1287
1293
  const { direction = "vertical", gap = "md", className = "" } = options;
1288
1294
  const gapClasses = { sm: "gap-2", md: "gap-4", lg: "gap-6" };
1289
1295
  const directionClasses = direction === "horizontal" ? "flex flex-row flex-wrap" : "flex flex-col";
1290
- return `<div class="${directionClasses} ${gapClasses[gap]} ${className}">
1296
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1297
+ return `<div class="${directionClasses} ${gapClasses[gap]} ${safeClassName}">
1291
1298
  ${cards.join("\n")}
1292
1299
  </div>`;
1293
1300
  }
1294
1301
 
1295
1302
  // libs/ui/src/components/form.ts
1303
+ var import_runtime2 = require("@frontmcp/uipack/runtime");
1296
1304
  function getInputSizeClasses(size) {
1297
1305
  const sizes = {
1298
1306
  sm: "px-3 py-1.5 text-sm",
@@ -1337,11 +1345,15 @@ function input(options) {
1337
1345
  className = "",
1338
1346
  data,
1339
1347
  iconBefore,
1340
- iconAfter
1348
+ iconAfter,
1349
+ sanitize = false
1341
1350
  } = options;
1351
+ const safeIconBefore = sanitize && iconBefore ? (0, import_runtime2.sanitizeHtmlContent)(iconBefore) : iconBefore;
1352
+ const safeIconAfter = sanitize && iconAfter ? (0, import_runtime2.sanitizeHtmlContent)(iconAfter) : iconAfter;
1342
1353
  const sizeClasses = getInputSizeClasses(size);
1343
1354
  const stateClasses = getInputStateClasses(state);
1344
- const hasIcon = iconBefore || iconAfter;
1355
+ const hasIcon = safeIconBefore || safeIconAfter;
1356
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1345
1357
  const baseClasses = [
1346
1358
  "w-full rounded-lg border bg-white",
1347
1359
  "transition-colors duration-200",
@@ -1349,8 +1361,8 @@ function input(options) {
1349
1361
  disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
1350
1362
  sizeClasses,
1351
1363
  stateClasses,
1352
- hasIcon ? (iconBefore ? "pl-10" : "") + (iconAfter ? " pr-10" : "") : "",
1353
- className
1364
+ hasIcon ? (safeIconBefore ? "pl-10" : "") + (safeIconAfter ? " pr-10" : "") : "",
1365
+ safeClassName
1354
1366
  ].filter(Boolean).join(" ");
1355
1367
  const dataAttrs = buildDataAttrs2(data);
1356
1368
  const inputAttrs = [
@@ -1375,8 +1387,8 @@ function input(options) {
1375
1387
  </label>` : "";
1376
1388
  const helperHtml = helper && !error ? `<p class="mt-1.5 text-sm text-text-secondary">${(0, import_utils2.escapeHtml)(helper)}</p>` : "";
1377
1389
  const errorHtml = error ? `<p class="mt-1.5 text-sm text-danger">${(0, import_utils2.escapeHtml)(error)}</p>` : "";
1378
- const iconBeforeHtml = iconBefore ? `<span class="absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary">${iconBefore}</span>` : "";
1379
- const iconAfterHtml = iconAfter ? `<span class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary">${iconAfter}</span>` : "";
1390
+ const iconBeforeHtml = safeIconBefore ? `<span class="absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconBefore}</span>` : "";
1391
+ const iconAfterHtml = safeIconAfter ? `<span class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconAfter}</span>` : "";
1380
1392
  const inputHtml = hasIcon ? `<div class="relative">
1381
1393
  ${iconBeforeHtml}
1382
1394
  <input ${inputAttrs}>
@@ -1408,6 +1420,7 @@ function select(options) {
1408
1420
  } = options;
1409
1421
  const sizeClasses = getInputSizeClasses(size);
1410
1422
  const stateClasses = getInputStateClasses(state);
1423
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1411
1424
  const baseClasses = [
1412
1425
  "w-full rounded-lg border bg-white",
1413
1426
  "transition-colors duration-200",
@@ -1415,7 +1428,7 @@ function select(options) {
1415
1428
  disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
1416
1429
  sizeClasses,
1417
1430
  stateClasses,
1418
- className
1431
+ safeClassName
1419
1432
  ].filter(Boolean).join(" ");
1420
1433
  const dataAttrs = buildDataAttrs2(data);
1421
1434
  const optionsHtml = selectOptions.map((opt) => {
@@ -1472,6 +1485,7 @@ function textarea(options) {
1472
1485
  horizontal: "resize-x",
1473
1486
  both: "resize"
1474
1487
  };
1488
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1475
1489
  const baseClasses = [
1476
1490
  "w-full rounded-lg border bg-white",
1477
1491
  "transition-colors duration-200",
@@ -1480,7 +1494,7 @@ function textarea(options) {
1480
1494
  sizeClasses,
1481
1495
  stateClasses,
1482
1496
  resizeClasses[resize],
1483
- className
1497
+ safeClassName
1484
1498
  ].filter(Boolean).join(" ");
1485
1499
  const dataAttrs = buildDataAttrs2(data);
1486
1500
  const labelHtml = label ? `<label for="${(0, import_utils2.escapeHtml)(id)}" class="block text-sm font-medium text-text-primary mb-1.5">
@@ -1524,7 +1538,8 @@ function checkbox(options) {
1524
1538
  ].join(" ");
1525
1539
  const helperHtml = helper && !error ? `<p class="text-sm text-text-secondary">${(0, import_utils2.escapeHtml)(helper)}</p>` : "";
1526
1540
  const errorHtml = error ? `<p class="text-sm text-danger">${(0, import_utils2.escapeHtml)(error)}</p>` : "";
1527
- return `<div class="form-field ${className}">
1541
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1542
+ return `<div class="form-field ${safeClassName}">
1528
1543
  <label class="flex items-start gap-3 ${disabled ? "cursor-not-allowed" : "cursor-pointer"}">
1529
1544
  <input
1530
1545
  type="checkbox"
@@ -1568,7 +1583,8 @@ function radioGroup(options) {
1568
1583
  const labelHtml = label ? `<label class="block text-sm font-medium text-text-primary mb-2">${(0, import_utils2.escapeHtml)(label)}</label>` : "";
1569
1584
  const helperHtml = helper && !error ? `<p class="mt-1.5 text-sm text-text-secondary">${(0, import_utils2.escapeHtml)(helper)}</p>` : "";
1570
1585
  const errorHtml = error ? `<p class="mt-1.5 text-sm text-danger">${(0, import_utils2.escapeHtml)(error)}</p>` : "";
1571
- return `<div class="form-field ${className}" role="radiogroup">
1586
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1587
+ return `<div class="form-field ${safeClassName}" role="radiogroup">
1572
1588
  ${labelHtml}
1573
1589
  <div class="${directionClasses}">
1574
1590
  ${radiosHtml}
@@ -1590,10 +1606,26 @@ function form(content, options = {}) {
1590
1606
  ].filter(Boolean).join(" ");
1591
1607
  return `<form ${attrs}>${content}</form>`;
1592
1608
  }
1609
+ var GRID_COLS_MAP = {
1610
+ 1: "grid-cols-1",
1611
+ 2: "grid-cols-2",
1612
+ 3: "grid-cols-3",
1613
+ 4: "grid-cols-4",
1614
+ 5: "grid-cols-5",
1615
+ 6: "grid-cols-6",
1616
+ 7: "grid-cols-7",
1617
+ 8: "grid-cols-8",
1618
+ 9: "grid-cols-9",
1619
+ 10: "grid-cols-10",
1620
+ 11: "grid-cols-11",
1621
+ 12: "grid-cols-12"
1622
+ };
1593
1623
  function formRow(fields, options = {}) {
1594
1624
  const { gap = "md", className = "" } = options;
1595
1625
  const gapClasses = { sm: "gap-2", md: "gap-4", lg: "gap-6" };
1596
- return `<div class="grid grid-cols-${fields.length} ${gapClasses[gap]} ${className}">
1626
+ const gridCols = GRID_COLS_MAP[fields.length] ?? "grid-cols-1";
1627
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1628
+ return `<div class="grid ${gridCols} ${gapClasses[gap]} ${safeClassName}">
1597
1629
  ${fields.join("\n")}
1598
1630
  </div>`;
1599
1631
  }
@@ -1603,7 +1635,8 @@ function formSection(content, options = {}) {
1603
1635
  <h3 class="text-lg font-semibold text-text-primary">${(0, import_utils2.escapeHtml)(title)}</h3>
1604
1636
  ${description ? `<p class="text-sm text-text-secondary mt-1">${(0, import_utils2.escapeHtml)(description)}</p>` : ""}
1605
1637
  </div>` : "";
1606
- return `<div class="form-section ${className}">
1638
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1639
+ return `<div class="form-section ${safeClassName}">
1607
1640
  ${headerHtml}
1608
1641
  <div class="space-y-4">
1609
1642
  ${content}
@@ -1618,7 +1651,8 @@ function formActions(buttons, options = {}) {
1618
1651
  right: "justify-end",
1619
1652
  between: "justify-between"
1620
1653
  };
1621
- return `<div class="flex items-center gap-3 pt-4 ${alignClasses[align]} ${className}">
1654
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1655
+ return `<div class="flex items-center gap-3 pt-4 ${alignClasses[align]} ${safeClassName}">
1622
1656
  ${buttons.join("\n")}
1623
1657
  </div>`;
1624
1658
  }
@@ -1630,6 +1664,7 @@ function csrfInput(token) {
1630
1664
  }
1631
1665
 
1632
1666
  // libs/ui/src/components/badge.ts
1667
+ var import_runtime3 = require("@frontmcp/uipack/runtime");
1633
1668
  function getVariantClasses3(variant) {
1634
1669
  const variants = {
1635
1670
  default: "bg-gray-100 text-gray-800",
@@ -1667,8 +1702,11 @@ function badge(text, options = {}) {
1667
1702
  icon,
1668
1703
  dot = false,
1669
1704
  className = "",
1670
- removable = false
1705
+ removable = false,
1706
+ sanitize = false
1671
1707
  } = options;
1708
+ const safeIcon = sanitize && icon ? (0, import_runtime3.sanitizeHtmlContent)(icon) : icon;
1709
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1672
1710
  if (dot) {
1673
1711
  const dotVariants = {
1674
1712
  default: "bg-gray-400",
@@ -1680,7 +1718,7 @@ function badge(text, options = {}) {
1680
1718
  info: "bg-blue-500",
1681
1719
  outline: "border border-current"
1682
1720
  };
1683
- const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant], className].filter(Boolean).join(" ");
1721
+ const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant], safeClassName].filter(Boolean).join(" ");
1684
1722
  return `<span class="${dotClasses}" aria-label="${(0, import_utils2.escapeHtml)(text)}" title="${(0, import_utils2.escapeHtml)(text)}"></span>`;
1685
1723
  }
1686
1724
  const variantClasses = getVariantClasses3(variant);
@@ -1690,9 +1728,9 @@ function badge(text, options = {}) {
1690
1728
  pill ? "rounded-full" : "rounded-md",
1691
1729
  variantClasses,
1692
1730
  sizeClasses,
1693
- className
1731
+ safeClassName
1694
1732
  ].filter(Boolean).join(" ");
1695
- const iconHtml = icon ? `<span class="mr-1">${icon}</span>` : "";
1733
+ const iconHtml = safeIcon ? `<span class="mr-1">${safeIcon}</span>` : "";
1696
1734
  const removeHtml = removable ? `<button
1697
1735
  type="button"
1698
1736
  class="ml-1.5 -mr-1 hover:opacity-70 transition-opacity"
@@ -1710,7 +1748,8 @@ function badge(text, options = {}) {
1710
1748
  function badgeGroup(badges, options = {}) {
1711
1749
  const { gap = "sm", className = "" } = options;
1712
1750
  const gapClasses = { sm: "gap-1", md: "gap-2", lg: "gap-3" };
1713
- return `<div class="inline-flex flex-wrap ${gapClasses[gap]} ${className}">
1751
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1752
+ return `<div class="inline-flex flex-wrap ${gapClasses[gap]} ${safeClassName}">
1714
1753
  ${badges.join("\n")}
1715
1754
  </div>`;
1716
1755
  }
@@ -1726,6 +1765,7 @@ var busyDot = (label = "Busy") => badge(label, { variant: "danger", dot: true })
1726
1765
  var awayDot = (label = "Away") => badge(label, { variant: "warning", dot: true });
1727
1766
 
1728
1767
  // libs/ui/src/components/alert.ts
1768
+ var import_runtime4 = require("@frontmcp/uipack/runtime");
1729
1769
  var alertIcons = {
1730
1770
  info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1731
1771
  <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"/>
@@ -1769,11 +1809,24 @@ function getVariantClasses4(variant) {
1769
1809
  return variants[variant];
1770
1810
  }
1771
1811
  function alert(message, options = {}) {
1772
- const { variant = "info", title, showIcon = true, icon, dismissible = false, className = "", id, actions } = options;
1812
+ const {
1813
+ variant = "info",
1814
+ title,
1815
+ showIcon = true,
1816
+ icon,
1817
+ dismissible = false,
1818
+ className = "",
1819
+ id,
1820
+ actions,
1821
+ sanitize = false
1822
+ } = options;
1823
+ const safeIcon = sanitize && icon ? (0, import_runtime4.sanitizeHtmlContent)(icon) : icon;
1824
+ const safeActions = sanitize && actions ? (0, import_runtime4.sanitizeHtmlContent)(actions) : actions;
1773
1825
  const variantClasses = getVariantClasses4(variant);
1774
- const baseClasses = ["rounded-lg border p-4", variantClasses.container, className].filter(Boolean).join(" ");
1826
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1827
+ const baseClasses = ["rounded-lg border p-4", variantClasses.container, safeClassName].filter(Boolean).join(" ");
1775
1828
  const iconHtml = showIcon ? `<div class="flex-shrink-0 ${variantClasses.icon}">
1776
- ${icon || alertIcons[variant]}
1829
+ ${safeIcon || alertIcons[variant]}
1777
1830
  </div>` : "";
1778
1831
  const titleHtml = title ? `<h3 class="font-semibold">${(0, import_utils2.escapeHtml)(title)}</h3>` : "";
1779
1832
  const dismissHtml = dismissible ? `<button
@@ -1786,7 +1839,7 @@ function alert(message, options = {}) {
1786
1839
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
1787
1840
  </svg>
1788
1841
  </button>` : "";
1789
- const actionsHtml = actions ? `<div class="mt-3">${actions}</div>` : "";
1842
+ const actionsHtml = safeActions ? `<div class="mt-3">${safeActions}</div>` : "";
1790
1843
  const idAttr = id ? `id="${(0, import_utils2.escapeHtml)(id)}"` : "";
1791
1844
  return `<div class="alert ${baseClasses}" role="alert" ${idAttr}>
1792
1845
  <div class="flex gap-3">
@@ -5258,7 +5311,17 @@ var ReactRendererAdapter = class {
5258
5311
  return true;
5259
5312
  }
5260
5313
  if (typeof content === "string") {
5261
- return content.includes("React.createElement") || content.includes("jsx(") || content.includes("jsxs(") || /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]*return\s*[\s\S]*</.test(content);
5314
+ if (content.includes("React.createElement") || content.includes("jsx(") || content.includes("jsxs(")) {
5315
+ return true;
5316
+ }
5317
+ const funcIndex = content.indexOf("function");
5318
+ if (funcIndex !== -1) {
5319
+ const afterFunc = content.slice(funcIndex, Math.min(funcIndex + 2e3, content.length));
5320
+ if (afterFunc.includes("return") && afterFunc.includes("<")) {
5321
+ return true;
5322
+ }
5323
+ }
5324
+ return false;
5262
5325
  }
5263
5326
  return false;
5264
5327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/ui",
3
- "version": "0.8.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.8.0"
67
+ "@frontmcp/uipack": "0.9.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/react": "^19.0.0",
@@ -250,7 +250,17 @@ var ReactRendererAdapter = class {
250
250
  return true;
251
251
  }
252
252
  if (typeof content === "string") {
253
- return content.includes("React.createElement") || content.includes("jsx(") || content.includes("jsxs(") || /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]*return\s*[\s\S]*</.test(content);
253
+ if (content.includes("React.createElement") || content.includes("jsx(") || content.includes("jsxs(")) {
254
+ return true;
255
+ }
256
+ const funcIndex = content.indexOf("function");
257
+ if (funcIndex !== -1) {
258
+ const afterFunc = content.slice(funcIndex, Math.min(funcIndex + 2e3, content.length));
259
+ if (afterFunc.includes("return") && afterFunc.includes("<")) {
260
+ return true;
261
+ }
262
+ }
263
+ return false;
254
264
  }
255
265
  return false;
256
266
  }
@@ -1 +1 @@
1
- {"version":3,"file":"react.adapter.d.ts","sourceRoot":"","sources":["../../src/renderers/react.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC5G,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAsDrD;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAW;IAGhC,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,WAAW,CAA8B;IAEjD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO;IAqB7C;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAMhG;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC;IA4DxB;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAI1G;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAsChF;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAQlC;;OAEG;YACW,iBAAiB;IAa/B;;OAEG;YACW,SAAS;IAmCvB;;OAEG;IACH,OAAO,CAAC,YAAY;CAsBrB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,eAAe,CAAC,CAEjE"}
1
+ {"version":3,"file":"react.adapter.d.ts","sourceRoot":"","sources":["../../src/renderers/react.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC5G,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAsDrD;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAW;IAGhC,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,WAAW,CAA8B;IAEjD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO;IAiC7C;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAMhG;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC;IA4DxB;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAI1G;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAsChF;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAQlC;;OAEG;YACW,iBAAiB;IAa/B;;OAEG;YACW,SAAS;IAmCvB;;OAEG;IACH,OAAO,CAAC,YAAY;CAsBrB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,eAAe,CAAC,CAEjE"}
@@ -287,6 +287,8 @@ function withFrontMCP(Component) {
287
287
 
288
288
  // libs/ui/src/universal/renderers/html.renderer.ts
289
289
  var import_react3 = __toESM(require("react"));
290
+ var import_runtime = require("@frontmcp/uipack/runtime");
291
+ var sanitizeHtml = import_runtime.sanitizeHtmlContent;
290
292
  var htmlRenderer = {
291
293
  type: "html",
292
294
  priority: 0,
@@ -305,13 +307,6 @@ var htmlRenderer = {
305
307
  });
306
308
  }
307
309
  };
308
- function sanitizeHtml(html) {
309
- let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
310
- sanitized = sanitized.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, "");
311
- sanitized = sanitized.replace(/\s+on\w+\s*=\s*[^\s>]*/gi, "");
312
- sanitized = sanitized.replace(/href\s*=\s*["']javascript:[^"']*["']/gi, 'href="#"');
313
- return sanitized;
314
- }
315
310
  var safeHtmlRenderer = {
316
311
  type: "html",
317
312
  priority: 0,
@@ -1177,7 +1172,7 @@ function buildMinimalRuntime(options) {
1177
1172
  }
1178
1173
 
1179
1174
  // libs/ui/src/universal/cached-runtime.ts
1180
- var import_runtime = require("@frontmcp/uipack/runtime");
1175
+ var import_runtime2 = require("@frontmcp/uipack/runtime");
1181
1176
  var import_build = require("@frontmcp/uipack/build");
1182
1177
  var RUNTIME_PLACEHOLDERS = {
1183
1178
  /** Placeholder for transpiled component code */
@@ -1655,7 +1650,7 @@ function getCachedRuntime(options, config = {}) {
1655
1650
  }
1656
1651
  const vendorParts = [];
1657
1652
  if (options.includeBridge) {
1658
- vendorParts.push((0, import_runtime.getMCPBridgeScript)());
1653
+ vendorParts.push((0, import_runtime2.getMCPBridgeScript)());
1659
1654
  }
1660
1655
  vendorParts.push(buildStoreRuntime2(), buildRequireShim());
1661
1656
  if (options.cdnType === "umd" || options.includeMarkdown) {
@@ -5,6 +5,12 @@
5
5
  * Used for pre-rendered or server-side generated HTML content.
6
6
  */
7
7
  import type { ClientRenderer } from '../types';
8
+ import { sanitizeHtmlContent } from '@frontmcp/uipack/runtime';
9
+ /**
10
+ * Re-export sanitizeHtmlContent as sanitizeHtml for backward compatibility.
11
+ * Uses DOMPurify in browser environments for robust sanitization.
12
+ */
13
+ export declare const sanitizeHtml: typeof sanitizeHtmlContent;
8
14
  /**
9
15
  * HTML renderer implementation.
10
16
  *
@@ -22,16 +28,9 @@ import type { ClientRenderer } from '../types';
22
28
  * ```
23
29
  */
24
30
  export declare const htmlRenderer: ClientRenderer;
25
- /**
26
- * Sanitize HTML string (basic XSS protection).
27
- * For production use, consider using a library like DOMPurify.
28
- *
29
- * @param html - HTML string to sanitize
30
- * @returns Sanitized HTML string
31
- */
32
- export declare function sanitizeHtml(html: string): string;
33
31
  /**
34
32
  * Create a safe HTML renderer that sanitizes content.
33
+ * Uses DOMPurify in browser environments for robust protection against XSS.
35
34
  */
36
35
  export declare const safeHtmlRenderer: ClientRenderer;
37
36
  //# sourceMappingURL=html.renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"html.renderer.d.ts","sourceRoot":"","sources":["../../../src/universal/renderers/html.renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,UAAU,CAAC;AAEhF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,YAAY,EAAE,cAsB1B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYjD;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAsB9B,CAAC"}
1
+ {"version":3,"file":"html.renderer.d.ts","sourceRoot":"","sources":["../../../src/universal/renderers/html.renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,YAAY,4BAAsB,CAAC;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,YAAY,EAAE,cAsB1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAsB9B,CAAC"}
@@ -652,6 +652,7 @@ function registerFmcpButton() {
652
652
  }
653
653
 
654
654
  // libs/ui/src/components/card.ts
655
+ var import_runtime = require("@frontmcp/uipack/runtime");
655
656
  function getVariantClasses2(variant) {
656
657
  const variants = {
657
658
  default: "bg-white border border-border rounded-xl shadow-sm",
@@ -686,33 +687,38 @@ function card(content, options = {}) {
686
687
  id,
687
688
  data,
688
689
  clickable = false,
689
- href
690
+ href,
691
+ sanitize = false
690
692
  } = options;
693
+ const safeContent = sanitize ? (0, import_runtime.sanitizeHtmlContent)(content) : content;
694
+ const safeHeaderActions = sanitize && headerActions ? (0, import_runtime.sanitizeHtmlContent)(headerActions) : headerActions;
695
+ const safeFooter = sanitize && footer ? (0, import_runtime.sanitizeHtmlContent)(footer) : footer;
691
696
  const variantClasses = getVariantClasses2(variant);
692
697
  const sizeClasses = getSizeClasses2(size);
693
698
  const clickableClasses = clickable ? "cursor-pointer hover:shadow-md transition-shadow" : "";
694
- const allClasses = [variantClasses, sizeClasses, clickableClasses, className].filter(Boolean).join(" ");
699
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
700
+ const allClasses = [variantClasses, sizeClasses, clickableClasses, safeClassName].filter(Boolean).join(" ");
695
701
  const dataAttrs = buildDataAttrs(data);
696
702
  const idAttr = id ? `id="${(0, import_utils2.escapeHtml)(id)}"` : "";
697
- const hasHeader = title || subtitle || headerActions;
703
+ const hasHeader = title || subtitle || safeHeaderActions;
698
704
  const headerHtml = hasHeader ? `<div class="flex items-start justify-between mb-4">
699
705
  <div>
700
706
  ${title ? `<h3 class="text-lg font-semibold text-text-primary">${(0, import_utils2.escapeHtml)(title)}</h3>` : ""}
701
707
  ${subtitle ? `<p class="text-sm text-text-secondary mt-1">${(0, import_utils2.escapeHtml)(subtitle)}</p>` : ""}
702
708
  </div>
703
- ${headerActions ? `<div class="flex items-center gap-2">${headerActions}</div>` : ""}
709
+ ${safeHeaderActions ? `<div class="flex items-center gap-2">${safeHeaderActions}</div>` : ""}
704
710
  </div>` : "";
705
- const footerHtml = footer ? `<div class="mt-4 pt-4 border-t border-divider">${footer}</div>` : "";
711
+ const footerHtml = safeFooter ? `<div class="mt-4 pt-4 border-t border-divider">${safeFooter}</div>` : "";
706
712
  if (href) {
707
713
  return `<a href="${(0, import_utils2.escapeHtml)(href)}" class="${allClasses}" ${idAttr} ${dataAttrs}>
708
714
  ${headerHtml}
709
- ${content}
715
+ ${safeContent}
710
716
  ${footerHtml}
711
717
  </a>`;
712
718
  }
713
719
  return `<div class="${allClasses}" ${idAttr} ${dataAttrs}>
714
720
  ${headerHtml}
715
- ${content}
721
+ ${safeContent}
716
722
  ${footerHtml}
717
723
  </div>`;
718
724
  }
@@ -835,6 +841,7 @@ function registerFmcpCard() {
835
841
  }
836
842
 
837
843
  // libs/ui/src/components/alert.ts
844
+ var import_runtime2 = require("@frontmcp/uipack/runtime");
838
845
  var alertIcons = {
839
846
  info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
840
847
  <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"/>
@@ -878,11 +885,24 @@ function getVariantClasses3(variant) {
878
885
  return variants[variant];
879
886
  }
880
887
  function alert(message, options = {}) {
881
- const { variant = "info", title, showIcon = true, icon, dismissible = false, className = "", id, actions } = options;
888
+ const {
889
+ variant = "info",
890
+ title,
891
+ showIcon = true,
892
+ icon,
893
+ dismissible = false,
894
+ className = "",
895
+ id,
896
+ actions,
897
+ sanitize = false
898
+ } = options;
899
+ const safeIcon = sanitize && icon ? (0, import_runtime2.sanitizeHtmlContent)(icon) : icon;
900
+ const safeActions = sanitize && actions ? (0, import_runtime2.sanitizeHtmlContent)(actions) : actions;
882
901
  const variantClasses = getVariantClasses3(variant);
883
- const baseClasses = ["rounded-lg border p-4", variantClasses.container, className].filter(Boolean).join(" ");
902
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
903
+ const baseClasses = ["rounded-lg border p-4", variantClasses.container, safeClassName].filter(Boolean).join(" ");
884
904
  const iconHtml = showIcon ? `<div class="flex-shrink-0 ${variantClasses.icon}">
885
- ${icon || alertIcons[variant]}
905
+ ${safeIcon || alertIcons[variant]}
886
906
  </div>` : "";
887
907
  const titleHtml = title ? `<h3 class="font-semibold">${(0, import_utils2.escapeHtml)(title)}</h3>` : "";
888
908
  const dismissHtml = dismissible ? `<button
@@ -895,7 +915,7 @@ function alert(message, options = {}) {
895
915
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
896
916
  </svg>
897
917
  </button>` : "";
898
- const actionsHtml = actions ? `<div class="mt-3">${actions}</div>` : "";
918
+ const actionsHtml = safeActions ? `<div class="mt-3">${safeActions}</div>` : "";
899
919
  const idAttr = id ? `id="${(0, import_utils2.escapeHtml)(id)}"` : "";
900
920
  return `<div class="alert ${baseClasses}" role="alert" ${idAttr}>
901
921
  <div class="flex gap-3">
@@ -1012,6 +1032,7 @@ function registerFmcpAlert() {
1012
1032
  }
1013
1033
 
1014
1034
  // libs/ui/src/components/badge.ts
1035
+ var import_runtime3 = require("@frontmcp/uipack/runtime");
1015
1036
  function getVariantClasses4(variant) {
1016
1037
  const variants = {
1017
1038
  default: "bg-gray-100 text-gray-800",
@@ -1049,8 +1070,11 @@ function badge(text, options = {}) {
1049
1070
  icon,
1050
1071
  dot = false,
1051
1072
  className = "",
1052
- removable = false
1073
+ removable = false,
1074
+ sanitize = false
1053
1075
  } = options;
1076
+ const safeIcon = sanitize && icon ? (0, import_runtime3.sanitizeHtmlContent)(icon) : icon;
1077
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1054
1078
  if (dot) {
1055
1079
  const dotVariants = {
1056
1080
  default: "bg-gray-400",
@@ -1062,7 +1086,7 @@ function badge(text, options = {}) {
1062
1086
  info: "bg-blue-500",
1063
1087
  outline: "border border-current"
1064
1088
  };
1065
- const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant], className].filter(Boolean).join(" ");
1089
+ const dotClasses = ["inline-block rounded-full", getSizeClasses3(size, true), dotVariants[variant], safeClassName].filter(Boolean).join(" ");
1066
1090
  return `<span class="${dotClasses}" aria-label="${(0, import_utils2.escapeHtml)(text)}" title="${(0, import_utils2.escapeHtml)(text)}"></span>`;
1067
1091
  }
1068
1092
  const variantClasses = getVariantClasses4(variant);
@@ -1072,9 +1096,9 @@ function badge(text, options = {}) {
1072
1096
  pill ? "rounded-full" : "rounded-md",
1073
1097
  variantClasses,
1074
1098
  sizeClasses,
1075
- className
1099
+ safeClassName
1076
1100
  ].filter(Boolean).join(" ");
1077
- const iconHtml = icon ? `<span class="mr-1">${icon}</span>` : "";
1101
+ const iconHtml = safeIcon ? `<span class="mr-1">${safeIcon}</span>` : "";
1078
1102
  const removeHtml = removable ? `<button
1079
1103
  type="button"
1080
1104
  class="ml-1.5 -mr-1 hover:opacity-70 transition-opacity"
@@ -1193,6 +1217,7 @@ function registerFmcpBadge() {
1193
1217
  }
1194
1218
 
1195
1219
  // libs/ui/src/components/form.ts
1220
+ var import_runtime4 = require("@frontmcp/uipack/runtime");
1196
1221
  function getInputSizeClasses(size) {
1197
1222
  const sizes = {
1198
1223
  sm: "px-3 py-1.5 text-sm",
@@ -1237,11 +1262,15 @@ function input(options) {
1237
1262
  className = "",
1238
1263
  data,
1239
1264
  iconBefore,
1240
- iconAfter
1265
+ iconAfter,
1266
+ sanitize = false
1241
1267
  } = options;
1268
+ const safeIconBefore = sanitize && iconBefore ? (0, import_runtime4.sanitizeHtmlContent)(iconBefore) : iconBefore;
1269
+ const safeIconAfter = sanitize && iconAfter ? (0, import_runtime4.sanitizeHtmlContent)(iconAfter) : iconAfter;
1242
1270
  const sizeClasses = getInputSizeClasses(size);
1243
1271
  const stateClasses = getInputStateClasses(state);
1244
- const hasIcon = iconBefore || iconAfter;
1272
+ const hasIcon = safeIconBefore || safeIconAfter;
1273
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1245
1274
  const baseClasses = [
1246
1275
  "w-full rounded-lg border bg-white",
1247
1276
  "transition-colors duration-200",
@@ -1249,8 +1278,8 @@ function input(options) {
1249
1278
  disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
1250
1279
  sizeClasses,
1251
1280
  stateClasses,
1252
- hasIcon ? (iconBefore ? "pl-10" : "") + (iconAfter ? " pr-10" : "") : "",
1253
- className
1281
+ hasIcon ? (safeIconBefore ? "pl-10" : "") + (safeIconAfter ? " pr-10" : "") : "",
1282
+ safeClassName
1254
1283
  ].filter(Boolean).join(" ");
1255
1284
  const dataAttrs = buildDataAttrs2(data);
1256
1285
  const inputAttrs = [
@@ -1275,8 +1304,8 @@ function input(options) {
1275
1304
  </label>` : "";
1276
1305
  const helperHtml = helper && !error ? `<p class="mt-1.5 text-sm text-text-secondary">${(0, import_utils2.escapeHtml)(helper)}</p>` : "";
1277
1306
  const errorHtml = error ? `<p class="mt-1.5 text-sm text-danger">${(0, import_utils2.escapeHtml)(error)}</p>` : "";
1278
- const iconBeforeHtml = iconBefore ? `<span class="absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary">${iconBefore}</span>` : "";
1279
- const iconAfterHtml = iconAfter ? `<span class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary">${iconAfter}</span>` : "";
1307
+ const iconBeforeHtml = safeIconBefore ? `<span class="absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconBefore}</span>` : "";
1308
+ const iconAfterHtml = safeIconAfter ? `<span class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary">${safeIconAfter}</span>` : "";
1280
1309
  const inputHtml = hasIcon ? `<div class="relative">
1281
1310
  ${iconBeforeHtml}
1282
1311
  <input ${inputAttrs}>
@@ -1308,6 +1337,7 @@ function select(options) {
1308
1337
  } = options;
1309
1338
  const sizeClasses = getInputSizeClasses(size);
1310
1339
  const stateClasses = getInputStateClasses(state);
1340
+ const safeClassName = className ? (0, import_utils2.escapeHtml)(className) : "";
1311
1341
  const baseClasses = [
1312
1342
  "w-full rounded-lg border bg-white",
1313
1343
  "transition-colors duration-200",
@@ -1315,7 +1345,7 @@ function select(options) {
1315
1345
  disabled ? "opacity-50 cursor-not-allowed bg-gray-50" : "",
1316
1346
  sizeClasses,
1317
1347
  stateClasses,
1318
- className
1348
+ safeClassName
1319
1349
  ].filter(Boolean).join(" ");
1320
1350
  const dataAttrs = buildDataAttrs2(data);
1321
1351
  const optionsHtml = selectOptions.map((opt) => {