@reblu/site-blocks 0.3.1 → 0.4.1

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.d.cts CHANGED
@@ -51,9 +51,13 @@ declare function buildShareUrls(url: string, title: string): ShareUrls;
51
51
  * `dangerouslySetInnerHTML` consumers listed above. `MarkdownRenderer` re-parses that
52
52
  * string through `html-react-parser` into a token-styled component map
53
53
  * (`markdown-renderer.tsx` `buildOptions`), and that map rebuilds most elements from
54
- * scratch — so on the blog-body path author attributes are dropped downstream of this
55
- * module regardless of what the schema allows. Widening the renderer's attribute
56
- * forwarding is a separate change; do not read these comments as a claim about it.
54
+ * scratch. It forwards only the subset of author attributes that carries MEANING
55
+ * (`id`, `lang`, `dir`, `title`, `hidden`, `role`, `aria-*`, `data-*`, and the list
56
+ * numbering attributes) — see `PRESERVED_GLOBAL_ATTRIBUTES` there for the line and
57
+ * why it sits where it does. `class` and `style` in particular are guaranteed by THIS
58
+ * module and still dropped by that one, because the map owns presentation (REB-666).
59
+ * Nothing the renderer forwards widens this schema: it can only pass through what
60
+ * `sanitizeHtml` already allowed.
57
61
  *
58
62
  * REB-606: this used to run on `isomorphic-dompurify`, which pulls `jsdom` into the
59
63
  * import graph. `jsdom`'s transitive `html-encoding-sniffer` → `@exodus/bytes` chain
package/dist/index.d.ts CHANGED
@@ -51,9 +51,13 @@ declare function buildShareUrls(url: string, title: string): ShareUrls;
51
51
  * `dangerouslySetInnerHTML` consumers listed above. `MarkdownRenderer` re-parses that
52
52
  * string through `html-react-parser` into a token-styled component map
53
53
  * (`markdown-renderer.tsx` `buildOptions`), and that map rebuilds most elements from
54
- * scratch — so on the blog-body path author attributes are dropped downstream of this
55
- * module regardless of what the schema allows. Widening the renderer's attribute
56
- * forwarding is a separate change; do not read these comments as a claim about it.
54
+ * scratch. It forwards only the subset of author attributes that carries MEANING
55
+ * (`id`, `lang`, `dir`, `title`, `hidden`, `role`, `aria-*`, `data-*`, and the list
56
+ * numbering attributes) — see `PRESERVED_GLOBAL_ATTRIBUTES` there for the line and
57
+ * why it sits where it does. `class` and `style` in particular are guaranteed by THIS
58
+ * module and still dropped by that one, because the map owns presentation (REB-666).
59
+ * Nothing the renderer forwards widens this schema: it can only pass through what
60
+ * `sanitizeHtml` already allowed.
57
61
  *
58
62
  * REB-606: this used to run on `isomorphic-dompurify`, which pulls `jsdom` into the
59
63
  * import graph. `jsdom`'s transitive `html-encoding-sniffer` → `@exodus/bytes` chain
package/dist/index.js CHANGED
@@ -13011,9 +13011,17 @@ function markdownToSafeHtml(markdown) {
13011
13011
  }
13012
13012
 
13013
13013
  // src/components/markdown-renderer.tsx
13014
- import { cn } from "@reblu/site-ui";
13015
- import parse3, { domToReact, Element } from "html-react-parser";
13014
+ import { cn } from "@reblu/site-ui/utils";
13015
+ import {
13016
+ domToReact,
13017
+ Element,
13018
+ htmlToDOM
13019
+ } from "html-react-parser";
13016
13020
  import { Fragment, jsx } from "react/jsx-runtime";
13021
+ var HTML_TO_DOM_OPTIONS = { lowerCaseAttributeNames: false };
13022
+ function parse3(html5, options) {
13023
+ return domToReact(htmlToDOM(html5, HTML_TO_DOM_OPTIONS), options);
13024
+ }
13017
13025
  var HEADING_CLASSES = {
13018
13026
  1: "text-3xl font-bold mt-8 mb-4 first:mt-0",
13019
13027
  2: "text-2xl font-bold mt-7 mb-3 first:mt-0",
@@ -13025,6 +13033,41 @@ var HEADING_CLASSES = {
13025
13033
  function hasClass(el, name) {
13026
13034
  return typeof el.attribs.class === "string" && el.attribs.class.split(/\s+/).includes(name);
13027
13035
  }
13036
+ var PRESERVED_GLOBAL_ATTRIBUTES = [
13037
+ // Anchor targets. In-document links ("see clause 4" → `#clause-4`) resolve to
13038
+ // nothing without them.
13039
+ "id",
13040
+ // Language and direction of a passage — a bilingual legal page is the normal case
13041
+ // here, and screen readers switch voice on it.
13042
+ "lang",
13043
+ "dir",
13044
+ // Advisory text the author wrote; it is content, not styling.
13045
+ "title",
13046
+ // Explicit "do not publish this". Without it a block authored as `<p hidden>`
13047
+ // becomes VISIBLE — publishing what the author marked as not-for-publication.
13048
+ "hidden",
13049
+ // Accessible semantics. `../lib/markdown` restores these through the sanitiser
13050
+ // precisely so they are not lost (REB-286's axe gate); dropping them one layer
13051
+ // later would undo that.
13052
+ "role"
13053
+ ];
13054
+ var PRESERVED_ATTRIBUTE_PREFIXES = ["aria-", "data-"];
13055
+ var PRESERVED_TAG_ATTRIBUTES = {
13056
+ ol: ["start", "type"],
13057
+ li: ["value", "type"]
13058
+ };
13059
+ var BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set(["hidden"]);
13060
+ function authorAttributes(el) {
13061
+ const tagSpecific = PRESERVED_TAG_ATTRIBUTES[el.name] ?? [];
13062
+ const preserved = {};
13063
+ for (const [rawName, value] of Object.entries(el.attribs)) {
13064
+ const name = rawName.toLowerCase();
13065
+ const keep = PRESERVED_GLOBAL_ATTRIBUTES.includes(name) || tagSpecific.includes(name) || PRESERVED_ATTRIBUTE_PREFIXES.some((prefix) => name.startsWith(prefix));
13066
+ if (!keep) continue;
13067
+ preserved[name] = BOOLEAN_ATTRIBUTES.has(name) ? true : value;
13068
+ }
13069
+ return preserved;
13070
+ }
13028
13071
  function isExternalHref(href) {
13029
13072
  return !!href && /^(https?:)?\/\//.test(href);
13030
13073
  }
@@ -13034,29 +13077,46 @@ function buildOptions(headingOffset) {
13034
13077
  if (!(node instanceof Element)) return void 0;
13035
13078
  const children2 = () => domToReact(node.children, options);
13036
13079
  const { name, attribs } = node;
13080
+ const author = authorAttributes(node);
13037
13081
  const headingMatch = /^h([1-6])$/.exec(name);
13038
13082
  if (headingMatch) {
13039
13083
  const rendered = Math.min(6, Number(headingMatch[1]) + headingOffset);
13040
13084
  const Tag = `h${rendered}`;
13041
- return /* @__PURE__ */ jsx(Tag, { className: HEADING_CLASSES[rendered], children: children2() });
13085
+ return /* @__PURE__ */ jsx(Tag, { ...author, className: HEADING_CLASSES[rendered], children: children2() });
13042
13086
  }
13043
13087
  switch (name) {
13044
13088
  case "p":
13045
- return /* @__PURE__ */ jsx("p", { className: "my-4 leading-relaxed text-foreground/90", children: children2() });
13089
+ return /* @__PURE__ */ jsx("p", { ...author, className: "my-4 leading-relaxed text-foreground/90", children: children2() });
13046
13090
  case "ul":
13047
- return /* @__PURE__ */ jsx("ul", { className: hasClass(node, "contains-task-list") ? "list-none pl-0 my-4 space-y-1" : "list-disc ml-6 my-4", children: children2() });
13091
+ return /* @__PURE__ */ jsx("ul", { ...author, className: hasClass(node, "contains-task-list") ? "list-none pl-0 my-4 space-y-1" : "list-disc ml-6 my-4", children: children2() });
13048
13092
  case "ol":
13049
- return /* @__PURE__ */ jsx("ol", { className: "list-decimal ml-6 my-4", children: children2() });
13093
+ return /* @__PURE__ */ jsx("ol", { ...author, className: "list-decimal ml-6 my-4", children: children2() });
13050
13094
  case "li":
13051
- return /* @__PURE__ */ jsx("li", { className: hasClass(node, "task-list-item") ? "flex items-start gap-2 my-1" : "my-1", children: children2() });
13095
+ return /* @__PURE__ */ jsx("li", { ...author, className: hasClass(node, "task-list-item") ? "flex items-start gap-2 my-1" : "my-1", children: children2() });
13052
13096
  case "blockquote":
13053
- return /* @__PURE__ */ jsx("blockquote", { className: "border-l-4 border-primary/50 pl-6 italic my-6 text-muted-foreground", children: children2() });
13097
+ return /* @__PURE__ */ jsx(
13098
+ "blockquote",
13099
+ {
13100
+ ...author,
13101
+ className: "border-l-4 border-primary/50 pl-6 italic my-6 text-muted-foreground",
13102
+ children: children2()
13103
+ }
13104
+ );
13054
13105
  case "pre":
13055
- return /* @__PURE__ */ jsx("pre", { className: "rounded-lg border border-input bg-muted p-4 my-6 overflow-x-auto font-mono text-sm", children: children2() });
13106
+ return /* @__PURE__ */ jsx(
13107
+ "pre",
13108
+ {
13109
+ ...author,
13110
+ className: "rounded-lg border border-input bg-muted p-4 my-6 overflow-x-auto font-mono text-sm",
13111
+ children: children2()
13112
+ }
13113
+ );
13056
13114
  case "code": {
13057
13115
  const isBlock = node.parent instanceof Element && node.parent.name === "pre";
13058
- if (isBlock) return /* @__PURE__ */ jsx("code", { className: "block", children: children2() });
13059
- return /* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-sm", children: children2() });
13116
+ if (isBlock) {
13117
+ return /* @__PURE__ */ jsx("code", { ...author, className: "block", children: children2() });
13118
+ }
13119
+ return /* @__PURE__ */ jsx("code", { ...author, className: "rounded bg-muted px-1.5 py-0.5 font-mono text-sm", children: children2() });
13060
13120
  }
13061
13121
  case "a": {
13062
13122
  const href = attribs.href;
@@ -13064,6 +13124,7 @@ function buildOptions(headingOffset) {
13064
13124
  return /* @__PURE__ */ jsx(
13065
13125
  "a",
13066
13126
  {
13127
+ ...author,
13067
13128
  href,
13068
13129
  className: "text-primary underline underline-offset-4 hover:text-primary/80",
13069
13130
  ...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
@@ -13080,6 +13141,7 @@ function buildOptions(headingOffset) {
13080
13141
  /* @__PURE__ */ jsx(
13081
13142
  "img",
13082
13143
  {
13144
+ ...author,
13083
13145
  src: attribs.src,
13084
13146
  alt: attribs.alt ?? "",
13085
13147
  loading: "lazy",
@@ -13091,6 +13153,7 @@ function buildOptions(headingOffset) {
13091
13153
  return /* @__PURE__ */ jsx("div", { className: "relative my-6 aspect-video w-full overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
13092
13154
  "iframe",
13093
13155
  {
13156
+ ...author,
13094
13157
  src: attribs.src,
13095
13158
  title: attribs.title ?? "Embedded video",
13096
13159
  allow: attribs.allow ?? "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
@@ -13099,13 +13162,13 @@ function buildOptions(headingOffset) {
13099
13162
  }
13100
13163
  ) });
13101
13164
  case "hr":
13102
- return /* @__PURE__ */ jsx("hr", { className: "my-8 border-muted-foreground/30" });
13165
+ return /* @__PURE__ */ jsx("hr", { ...author, className: "my-8 border-muted-foreground/30" });
13103
13166
  case "strong":
13104
- return /* @__PURE__ */ jsx("strong", { className: "font-bold text-foreground", children: children2() });
13167
+ return /* @__PURE__ */ jsx("strong", { ...author, className: "font-bold text-foreground", children: children2() });
13105
13168
  case "em":
13106
- return /* @__PURE__ */ jsx("em", { className: "italic", children: children2() });
13169
+ return /* @__PURE__ */ jsx("em", { ...author, className: "italic", children: children2() });
13107
13170
  case "del":
13108
- return /* @__PURE__ */ jsx("del", { className: "line-through", children: children2() });
13171
+ return /* @__PURE__ */ jsx("del", { ...author, className: "line-through", children: children2() });
13109
13172
  default:
13110
13173
  return void 0;
13111
13174
  }
@@ -13124,10 +13187,11 @@ function SafeHtml({ html: html5, className, headingOffset = 0 }) {
13124
13187
  }
13125
13188
 
13126
13189
  // src/components/treatment.tsx
13127
- import { cn as cn3 } from "@reblu/site-ui";
13190
+ import { cn as cn3 } from "@reblu/site-ui/utils";
13128
13191
 
13129
13192
  // src/components/_page-parts.tsx
13130
- import { Button, cn as cn2 } from "@reblu/site-ui";
13193
+ import { cn as cn2 } from "@reblu/site-ui/utils";
13194
+ import { buttonVariants } from "@reblu/site-ui/button-variants";
13131
13195
  import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
13132
13196
  var KNOWN_BUTTON_VARIANTS = /* @__PURE__ */ new Set([
13133
13197
  "default",
@@ -13158,14 +13222,19 @@ function PageHero({ page }) {
13158
13222
  }
13159
13223
  function SectionCta({ section }) {
13160
13224
  if (!section.ctaLabel || !section.ctaUrl) return null;
13161
- return /* @__PURE__ */ jsx2("div", { className: "pt-2", children: /* @__PURE__ */ jsx2(Button, { asChild: true, variant: toButtonVariant(section.ctaVariant), children: /* @__PURE__ */ jsx2(
13225
+ const variant = toButtonVariant(section.ctaVariant);
13226
+ return /* @__PURE__ */ jsx2("div", { className: "pt-2", children: /* @__PURE__ */ jsx2(
13162
13227
  "a",
13163
13228
  {
13164
13229
  href: section.ctaUrl,
13230
+ "data-slot": "button",
13231
+ "data-variant": variant,
13232
+ "data-size": "default",
13233
+ className: cn2(buttonVariants({ variant, size: "default" })),
13165
13234
  ...section.ctaIsExternal ? { target: "_blank", rel: "noopener noreferrer" } : {},
13166
13235
  children: section.ctaLabel
13167
13236
  }
13168
- ) }) });
13237
+ ) });
13169
13238
  }
13170
13239
  function SectionImages({ section }) {
13171
13240
  if (section.images.length === 0) return null;
@@ -13213,7 +13282,7 @@ function Treatment({ page, className }) {
13213
13282
  }
13214
13283
 
13215
13284
  // src/components/specialty.tsx
13216
- import { cn as cn4 } from "@reblu/site-ui";
13285
+ import { cn as cn4 } from "@reblu/site-ui/utils";
13217
13286
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
13218
13287
  function Specialty({
13219
13288
  page,
@@ -13254,7 +13323,7 @@ function Specialty({
13254
13323
  }
13255
13324
 
13256
13325
  // src/components/profile-card.tsx
13257
- import { cn as cn5 } from "@reblu/site-ui";
13326
+ import { cn as cn5 } from "@reblu/site-ui/utils";
13258
13327
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
13259
13328
  function initials(name) {
13260
13329
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
@@ -13296,7 +13365,7 @@ function ProfileCard({ name, role, imageUrl, specialties = [], bio, href, classN
13296
13365
  }
13297
13366
 
13298
13367
  // src/components/product-card.tsx
13299
- import { cn as cn6 } from "@reblu/site-ui";
13368
+ import { cn as cn6 } from "@reblu/site-ui/utils";
13300
13369
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
13301
13370
  function ProductCard({ product, href, currencyCode = "EUR", locale, className }) {
13302
13371
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];