@bettercms-ai/ui 0.6.1 → 0.7.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/registry.js CHANGED
@@ -47,7 +47,13 @@ var PT_TYPES = {
47
47
  */
48
48
  table: "table",
49
49
  /** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */
50
- link: "link"
50
+ link: "link",
51
+ /**
52
+ * The annotation carrying a site's OWN inline markup — `<span class>`, `<small>`, `<time
53
+ * datetime>`. Unprefixed, like `table`, because it describes HTML rather than a BetterCMS
54
+ * concept. See `styled.ts` for what it may hold and why the list is that short.
55
+ */
56
+ styled: "styled"
51
57
  };
52
58
  var BUILTIN_TABLE_KEY = "builtin:table";
53
59
  var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
@@ -59,6 +65,66 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
59
65
  PT_TYPES.mention
60
66
  ]);
61
67
 
68
+ // ../richtext/src/styled.ts
69
+ var STYLED_TAGS = ["span", "small", "sup", "sub", "kbd", "abbr", "time"];
70
+ var TAG_SET = new Set(STYLED_TAGS);
71
+ var isStyledTag = (tag) => typeof tag === "string" && TAG_SET.has(tag);
72
+ var ATTR_NAMES = /* @__PURE__ */ new Set(["title", "datetime", "style", "aria-label"]);
73
+ var DATA_ATTR = /^data-[a-z][a-z0-9-]*$/;
74
+ var isStyledAttr = (name) => ATTR_NAMES.has(name) || DATA_ATTR.test(name) && !name.startsWith("data-bcms-");
75
+ var STYLE_PROPS = /* @__PURE__ */ new Set([
76
+ "color",
77
+ "background-color",
78
+ "opacity",
79
+ "font",
80
+ "font-family",
81
+ "font-size",
82
+ "font-style",
83
+ "font-weight",
84
+ "font-variant",
85
+ "font-feature-settings",
86
+ "font-variation-settings",
87
+ "letter-spacing",
88
+ "line-height",
89
+ "word-spacing",
90
+ "white-space",
91
+ "word-break",
92
+ "text-align",
93
+ "text-decoration",
94
+ "text-decoration-color",
95
+ "text-decoration-line",
96
+ "text-decoration-style",
97
+ "text-transform",
98
+ "text-underline-offset",
99
+ "vertical-align"
100
+ ]);
101
+ var STYLE_VALUE_BAN = /url\(|expression\(|@import|\\|\/\*|[<>]/i;
102
+ function safeStyle(css) {
103
+ const kept = [];
104
+ for (const declaration of css.split(";")) {
105
+ const at = declaration.indexOf(":");
106
+ if (at < 0) continue;
107
+ const name = declaration.slice(0, at).trim().toLowerCase();
108
+ const value = declaration.slice(at + 1).trim();
109
+ if (!name || !value) continue;
110
+ if (!name.startsWith("--") && !STYLE_PROPS.has(name)) continue;
111
+ if (STYLE_VALUE_BAN.test(value)) continue;
112
+ kept.push(`${name}:${value}`);
113
+ }
114
+ return kept.length ? kept.join(";") : void 0;
115
+ }
116
+ function styledAttrEntries(attrs) {
117
+ if (!attrs || typeof attrs !== "object" || Array.isArray(attrs)) return [];
118
+ const out = [];
119
+ for (const [name, raw] of Object.entries(attrs)) {
120
+ if (!isStyledAttr(name) || typeof raw !== "string") continue;
121
+ const value = name === "style" ? safeStyle(raw) : raw;
122
+ if (value === void 0) continue;
123
+ out.push([name, value]);
124
+ }
125
+ return out;
126
+ }
127
+
62
128
  // ../richtext/src/href.ts
63
129
  var SAFE_HREF_SCHEMES = ["http:", "https:", "mailto:", "tel:"];
64
130
  function safeHref(raw) {
@@ -233,6 +299,18 @@ function readStructuredContent(props) {
233
299
  // src/blocks/portableText.tsx
234
300
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
235
301
  var TOKEN = /^[a-z0-9-]+$/i;
302
+ function styleObject(css) {
303
+ const out = {};
304
+ for (const declaration of css.split(";")) {
305
+ const at = declaration.indexOf(":");
306
+ if (at < 0) continue;
307
+ const name = declaration.slice(0, at).trim();
308
+ const value = declaration.slice(at + 1).trim();
309
+ if (!name || !value) continue;
310
+ out[name.startsWith("--") ? name : name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = value;
311
+ }
312
+ return out;
313
+ }
236
314
  function Bound({
237
315
  base,
238
316
  nodeKey,
@@ -303,6 +381,36 @@ function buildComponents() {
303
381
  const blank = v.target === "_blank";
304
382
  return /* @__PURE__ */ jsx2("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
305
383
  },
384
+ /**
385
+ * The site's own inline markup, rendered as itself — the React half of
386
+ * `portableTextToHtml`'s `styled` mark, and it has to stay the same shape: Astro and the
387
+ * editor canvas render through that serialiser while a Next site renders through this
388
+ * one, and two renderers of one value that disagree is the whole bug class.
389
+ *
390
+ * The tag is re-checked against `STYLED_TAGS` and the attribute bag re-filtered, because
391
+ * a markDef is DATA and arrives from imports, the Management API and AI writes. React
392
+ * escapes the values it writes; the allowlist is what keeps a `href` or an `onClick` out
393
+ * of the props entirely. An unrecognised tag renders its CHILDREN — the words are the
394
+ * author's, the wrapper is only the design.
395
+ */
396
+ [PT_TYPES.styled]: ({ value, children }) => {
397
+ const v = value ?? {};
398
+ const tag = typeof v.tag === "string" ? v.tag.toLowerCase() : "";
399
+ if (!isStyledTag(tag)) return /* @__PURE__ */ jsx2(Fragment, { children });
400
+ const Tag = tag;
401
+ const className = typeof v.className === "string" && v.className ? v.className : void 0;
402
+ const attrs = styledAttrEntries(v.attrs);
403
+ const style = attrs.find(([name]) => name === "style")?.[1];
404
+ return /* @__PURE__ */ jsx2(
405
+ Tag,
406
+ {
407
+ className,
408
+ ...Object.fromEntries(attrs.filter(([name]) => name !== "style")),
409
+ ...style ? { style: styleObject(style) } : {},
410
+ children
411
+ }
412
+ );
413
+ },
306
414
  highlight: ({ children }) => /* @__PURE__ */ jsx2("mark", { children }),
307
415
  "strike-through": ({ children }) => /* @__PURE__ */ jsx2("s", { children }),
308
416
  /**