@reblu/site-blocks 0.17.0 → 0.18.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.d.cts CHANGED
@@ -227,6 +227,14 @@ interface MarkdownRendererProps {
227
227
  * the page keeps a single h1.
228
228
  */
229
229
  headingOffset?: number;
230
+ /**
231
+ * Land the body's SHALLOWEST heading at `1 + headingOffset` instead of shifting every
232
+ * heading by a fixed amount, so standard Markdown (`##` sections) renders `<h2>` under
233
+ * the page `<h1>` just like the convention (`#` sections) does. Relative depth is kept
234
+ * and levels clamp at h6. Use it where the body sits directly under the page `<h1>`;
235
+ * leave it off where the body sits under its own section heading. Default `false`.
236
+ */
237
+ normalizeHeadings?: boolean;
230
238
  /**
231
239
  * Restrict and render images (REB-1075). Absent: every image the sanitiser keeps is
232
240
  * rendered as a lazy `<img>` inside its paragraph (the historical behaviour).
@@ -247,7 +255,7 @@ interface MarkdownImageOptions {
247
255
  alt: string;
248
256
  }) => React$1.ReactNode;
249
257
  }
250
- declare function MarkdownRenderer({ content, className, headingOffset, images, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
258
+ declare function MarkdownRenderer({ content, className, headingOffset, normalizeHeadings, images, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
251
259
  /**
252
260
  * Render an **already-sanitised** HTML string through the same token-driven
253
261
  * component map as {@link MarkdownRenderer}. Consumers pass HTML that has been run
@@ -258,8 +266,10 @@ interface SafeHtmlProps {
258
266
  html: string | null | undefined;
259
267
  className?: string;
260
268
  headingOffset?: number;
269
+ /** See {@link MarkdownRendererProps.normalizeHeadings}. Default `false`. */
270
+ normalizeHeadings?: boolean;
261
271
  }
262
- declare function SafeHtml({ html, className, headingOffset }: SafeHtmlProps): react_jsx_runtime.JSX.Element | null;
272
+ declare function SafeHtml({ html, className, headingOffset, normalizeHeadings, }: SafeHtmlProps): react_jsx_runtime.JSX.Element | null;
263
273
 
264
274
  /** A heading level a caller can configure — `h1`…`h6`. */
265
275
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
package/dist/index.d.ts CHANGED
@@ -227,6 +227,14 @@ interface MarkdownRendererProps {
227
227
  * the page keeps a single h1.
228
228
  */
229
229
  headingOffset?: number;
230
+ /**
231
+ * Land the body's SHALLOWEST heading at `1 + headingOffset` instead of shifting every
232
+ * heading by a fixed amount, so standard Markdown (`##` sections) renders `<h2>` under
233
+ * the page `<h1>` just like the convention (`#` sections) does. Relative depth is kept
234
+ * and levels clamp at h6. Use it where the body sits directly under the page `<h1>`;
235
+ * leave it off where the body sits under its own section heading. Default `false`.
236
+ */
237
+ normalizeHeadings?: boolean;
230
238
  /**
231
239
  * Restrict and render images (REB-1075). Absent: every image the sanitiser keeps is
232
240
  * rendered as a lazy `<img>` inside its paragraph (the historical behaviour).
@@ -247,7 +255,7 @@ interface MarkdownImageOptions {
247
255
  alt: string;
248
256
  }) => React$1.ReactNode;
249
257
  }
250
- declare function MarkdownRenderer({ content, className, headingOffset, images, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
258
+ declare function MarkdownRenderer({ content, className, headingOffset, normalizeHeadings, images, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
251
259
  /**
252
260
  * Render an **already-sanitised** HTML string through the same token-driven
253
261
  * component map as {@link MarkdownRenderer}. Consumers pass HTML that has been run
@@ -258,8 +266,10 @@ interface SafeHtmlProps {
258
266
  html: string | null | undefined;
259
267
  className?: string;
260
268
  headingOffset?: number;
269
+ /** See {@link MarkdownRendererProps.normalizeHeadings}. Default `false`. */
270
+ normalizeHeadings?: boolean;
261
271
  }
262
- declare function SafeHtml({ html, className, headingOffset }: SafeHtmlProps): react_jsx_runtime.JSX.Element | null;
272
+ declare function SafeHtml({ html, className, headingOffset, normalizeHeadings, }: SafeHtmlProps): react_jsx_runtime.JSX.Element | null;
263
273
 
264
274
  /** A heading level a caller can configure — `h1`…`h6`. */
265
275
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
package/dist/index.js CHANGED
@@ -13131,8 +13131,25 @@ function slotClassName(structural, defaults, consumer) {
13131
13131
  // src/components/markdown-renderer.tsx
13132
13132
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13133
13133
  var HTML_TO_DOM_OPTIONS = { lowerCaseAttributeNames: false };
13134
- function parse3(html5, options) {
13135
- return domToReact(htmlToDOM(html5, HTML_TO_DOM_OPTIONS), options);
13134
+ function shallowestHeadingLevel(nodes) {
13135
+ let shallowest = null;
13136
+ for (const node of nodes) {
13137
+ if (!(node instanceof Element)) continue;
13138
+ const match = /^h([1-6])$/.exec(node.name);
13139
+ const level = match ? Number(match[1]) : shallowestHeadingLevel(node.children);
13140
+ if (level !== null && (shallowest === null || level < shallowest)) shallowest = level;
13141
+ }
13142
+ return shallowest;
13143
+ }
13144
+ function headingShift(nodes, headingOffset, normalize2) {
13145
+ if (!normalize2) return headingOffset;
13146
+ const shallowest = shallowestHeadingLevel(nodes);
13147
+ return shallowest === null ? headingOffset : 1 + headingOffset - shallowest;
13148
+ }
13149
+ function parse3(html5, headingOffset, normalizeHeadings, images) {
13150
+ const nodes = htmlToDOM(html5, HTML_TO_DOM_OPTIONS);
13151
+ const shift = headingShift(nodes, headingOffset, normalizeHeadings);
13152
+ return domToReact(nodes, buildOptions(shift, images));
13136
13153
  }
13137
13154
  function isAllowedImage(src, prefixes) {
13138
13155
  if (!src) return false;
@@ -13190,6 +13207,14 @@ function authorAttributes(el) {
13190
13207
  }
13191
13208
  return preserved;
13192
13209
  }
13210
+ var CELL_ALIGN_CLASSES = {
13211
+ left: "text-left",
13212
+ center: "text-center",
13213
+ right: "text-right"
13214
+ };
13215
+ function cellAlignClass(align) {
13216
+ return align ? CELL_ALIGN_CLASSES[align.toLowerCase()] : void 0;
13217
+ }
13193
13218
  function isExternalHref(href) {
13194
13219
  return !!href && /^(https?:)?\/\//.test(href);
13195
13220
  }
@@ -13241,7 +13266,7 @@ function buildOptions(headingOffset, images) {
13241
13266
  const author = authorAttributes(node);
13242
13267
  const headingMatch = /^h([1-6])$/.exec(name);
13243
13268
  if (headingMatch) {
13244
- const rendered = Math.min(6, Number(headingMatch[1]) + headingOffset);
13269
+ const rendered = Math.max(1, Math.min(6, Number(headingMatch[1]) + headingOffset));
13245
13270
  const Tag = `h${rendered}`;
13246
13271
  return /* @__PURE__ */ jsx(Tag, { ...author, className: HEADING_CLASSES[rendered], children: children2() });
13247
13272
  }
@@ -13323,6 +13348,36 @@ function buildOptions(headingOffset, images) {
13323
13348
  className: "absolute inset-0 h-full w-full border-0"
13324
13349
  }
13325
13350
  ) });
13351
+ // GFM tables (a cookie policy's cookie list, a price table). The table scrolls
13352
+ // inside its own wrapper so a wide one never pushes the page sideways on a phone.
13353
+ case "table":
13354
+ return /* @__PURE__ */ jsx("div", { className: "my-6 w-full overflow-x-auto", children: /* @__PURE__ */ jsx("table", { ...author, className: "w-full border-collapse text-sm", children: children2() }) });
13355
+ case "thead":
13356
+ return /* @__PURE__ */ jsx("thead", { ...author, className: "bg-muted", children: children2() });
13357
+ case "tbody":
13358
+ return /* @__PURE__ */ jsx("tbody", { ...author, children: children2() });
13359
+ case "tr":
13360
+ return /* @__PURE__ */ jsx("tr", { ...author, children: children2() });
13361
+ case "th":
13362
+ return /* @__PURE__ */ jsx(
13363
+ "th",
13364
+ {
13365
+ ...author,
13366
+ className: `border border-border px-3 py-2 font-semibold text-foreground ${cellAlignClass(attribs.align) ?? "text-left"}`,
13367
+ children: children2()
13368
+ }
13369
+ );
13370
+ case "td": {
13371
+ const align = cellAlignClass(attribs.align);
13372
+ return /* @__PURE__ */ jsx(
13373
+ "td",
13374
+ {
13375
+ ...author,
13376
+ className: `border border-border px-3 py-2 align-top text-foreground/90${align ? ` ${align}` : ""}`,
13377
+ children: children2()
13378
+ }
13379
+ );
13380
+ }
13326
13381
  case "hr":
13327
13382
  return /* @__PURE__ */ jsx("hr", { ...author, className: "my-8 border-muted-foreground/30" });
13328
13383
  case "strong":
@@ -13342,15 +13397,21 @@ function MarkdownRenderer({
13342
13397
  content,
13343
13398
  className,
13344
13399
  headingOffset = 0,
13400
+ normalizeHeadings = false,
13345
13401
  images
13346
13402
  }) {
13347
13403
  const html5 = markdownToSafeHtml(content);
13348
13404
  if (!html5) return null;
13349
- return /* @__PURE__ */ jsx("div", { className: slotClassName("max-w-none", "", className), "data-slot": "markdown", children: parse3(html5, buildOptions(headingOffset, images)) });
13405
+ return /* @__PURE__ */ jsx("div", { className: slotClassName("max-w-none", "", className), "data-slot": "markdown", children: parse3(html5, headingOffset, normalizeHeadings, images) });
13350
13406
  }
13351
- function SafeHtml({ html: html5, className, headingOffset = 0 }) {
13407
+ function SafeHtml({
13408
+ html: html5,
13409
+ className,
13410
+ headingOffset = 0,
13411
+ normalizeHeadings = false
13412
+ }) {
13352
13413
  if (!html5) return null;
13353
- return /* @__PURE__ */ jsx("div", { className: slotClassName("max-w-none", "", className), "data-slot": "safe-html", children: parse3(html5, buildOptions(headingOffset)) });
13414
+ return /* @__PURE__ */ jsx("div", { className: slotClassName("max-w-none", "", className), "data-slot": "safe-html", children: parse3(html5, headingOffset, normalizeHeadings) });
13354
13415
  }
13355
13416
 
13356
13417
  // src/components/course-description.tsx