@dogsbay/format-astro 0.2.0-beta.159 → 0.2.0-beta.160

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/serialize.js CHANGED
@@ -1,4 +1,4 @@
1
- import { walkInline, applyTextFlags, renderLeaf, indent, isSafeHref, apiPageLayout, apiSymbolLevels, renderAroundGroup, isDetailsSheet, stepLead, stepTitle, } from "@dogsbay/serialize-core";
1
+ import { walkInline, applyTextFlags, renderLeaf, indent, isSafeHref, apiPageLayout, apiSymbolLevels, renderAroundGroup, isDetailsSheet, isEmptyTableHead, cardBodyHasLink, stepLead, stepTitle, } from "@dogsbay/serialize-core";
2
2
  import { parseInline } from "@dogsbay/format-dogsbay-md";
3
3
  import { decorateDiff, hasDiffMarks } from "./diff-decoration.js";
4
4
  import { TAB_GLYPH, tabDiffSignal } from "@dogsbay/render-html/diff";
@@ -310,6 +310,9 @@ function nodeToAstroBase(node, ctx) {
310
310
  case "table":
311
311
  return tableToAstro(node, ctx);
312
312
  case "thead":
313
+ // a header row with no text (`| | |`, a headerless table) is no header (as render-html)
314
+ if (isEmptyTableHead(node))
315
+ return "";
313
316
  ctx.imports.add("table");
314
317
  return `<TableHeader>\n${childrenToAstro(node, ctx)}\n</TableHeader>`;
315
318
  case "tbody":
@@ -656,9 +659,12 @@ function proseToAstro(node, ctx) {
656
659
  }
657
660
  /** TableHead's classes, for a header row's empty cell written as a plain `td`. */
658
661
  const EMPTY_HEADER_CELL_CLASS = "px-2 py-2 text-left align-bottom font-semibold text-muted-foreground";
659
- /** A card title's heading: one level below the last heading on the page, h6 at most. */
660
- function cardTitleTag(ctx) {
661
- return `h${Math.min((ctx.lastHeading ?? 1) + 1, 6)}`;
662
+ /**
663
+ * A card's title is text styled as one, not a heading (as render-html): a grid of cards is not a
664
+ * section of the page, and as headings its titles filled the outline and a reader's heading list.
665
+ */
666
+ function cardTitleTag(_ctx) {
667
+ return "div";
662
668
  }
663
669
  function headingToAstro(node, ctx) {
664
670
  const level = node.props?.level ?? 1;
@@ -1528,6 +1534,8 @@ function wholeCardLink(yes, fn) {
1528
1534
  }
1529
1535
  /** A card's title: not-prose, or docs.css's h3 rule (1.25rem, 2rem above) applies inside the card. */
1530
1536
  const CARD_TITLE_CLASS = "not-prose m-0 border-0 p-0 text-base font-semibold";
1537
+ /** Marks a card title for styles and tests (it is a div, not a heading). */
1538
+ const CARD_TITLE_ATTR = "data-card-title";
1531
1539
  /**
1532
1540
  * A card group's columns, responsive as render-html draws them (its GRID_COLS): one column on a
1533
1541
  * phone, then up to `cols` (1–6) by Grid's breakpoints (0, 640, 768, 1024). Default 2. A list
@@ -1562,10 +1570,12 @@ function cardsToAstro(node, ctx) {
1562
1570
  const before = ctx.lastHeading;
1563
1571
  // Description sources: inline content, children, or props.description.
1564
1572
  // Child blocks carry their own <p>, so they get a <div>: <p><p>…</p></p> is invalid HTML.
1565
- const blocks = wholeCardLink(!!href, () => childrenToAstro(card, ctx));
1573
+ // A body with links of its own: the title links, not the whole card (cardBodyHasLink)
1574
+ const whole = !!href && (!title || !cardBodyHasLink([card.children, card.inline]));
1575
+ const blocks = wholeCardLink(whole, () => childrenToAstro(card, ctx));
1566
1576
  ctx.lastHeading = before;
1567
1577
  const inner = blocks
1568
- || (card.inline ? wholeCardLink(!!href, () => inlineToTemplate(card.inline)) : "")
1578
+ || (card.inline ? wholeCardLink(whole, () => inlineToTemplate(card.inline)) : "")
1569
1579
  || (description ? escapeTemplate(description) : "");
1570
1580
  const descTag = blocks ? "div" : "p";
1571
1581
  // Icon renders inline before the title — picks up Lucide
@@ -1577,7 +1587,8 @@ function cardsToAstro(node, ctx) {
1577
1587
  if (icon) {
1578
1588
  iconHtml = cardIconHtml(icon, title, ctx);
1579
1589
  }
1580
- const titleHtml = title ? `<${tag} class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</${tag}>` : "";
1590
+ const titleText = href && !whole ? `<a href="${escapeAttr(href)}">${escapeTemplate(title)}</a>` : escapeTemplate(title);
1591
+ const titleHtml = title ? `<${tag} class="${CARD_TITLE_CLASS}" ${CARD_TITLE_ATTR}>${titleText}</${tag}>` : "";
1581
1592
  // A description and a body are both shown (the description used to be dropped when a body existed).
1582
1593
  const lead = blocks && description
1583
1594
  ? `<p class="text-sm text-muted-foreground mt-1 [text-decoration:none]">${escapeTemplate(description)}</p>`
@@ -1588,14 +1599,14 @@ function cardsToAstro(node, ctx) {
1588
1599
  // A tone (TONES; the card's own, else the group's) sets the card's
1589
1600
  // border and tint: Card's tailwind-variants merge replaces bg-card
1590
1601
  const toned = lookup(CARD_TONE_CLASSES, card.props?.tone ?? node.props?.tone);
1591
- const cardDefaultClass = href
1602
+ const cardDefaultClass = whole
1592
1603
  ? toned ? `p-4 ${toned} hover:shadow-md transition-colors` : "p-4 hover:border-foreground/20 transition-colors"
1593
1604
  : toned ? `p-4 ${toned}` : "p-4";
1594
1605
  const cardClassAttr = mergeClassAttr(cardDefaultClass, card.props?.class);
1595
1606
  const cardPass = renderPassthroughAttrs(card.props, [
1596
1607
  "title", "href", "description", "icon", "variant", "class", "tone",
1597
1608
  ]);
1598
- if (href) {
1609
+ if (whole) {
1599
1610
  return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" data-variant="card" class="[text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1600
1611
  }
1601
1612
  return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n${iconHtml}\n${titleHtml}\n${desc}\n</CardContent>\n</Card>`;
@@ -1632,16 +1643,17 @@ function standaloneCardToAstro(node, ctx) {
1632
1643
  const tag = cardTitleTag(ctx);
1633
1644
  const before = ctx.lastHeading;
1634
1645
  // Child blocks carry their own <p>, so they get a <div>: <p><p>…</p></p> is invalid HTML.
1635
- const blocks = wholeCardLink(!!href, () => childrenToAstro(node, ctx));
1646
+ const whole = !!href && (!title || !cardBodyHasLink([node.children, node.inline]));
1647
+ const blocks = wholeCardLink(whole, () => childrenToAstro(node, ctx));
1636
1648
  ctx.lastHeading = before;
1637
1649
  const inner = blocks
1638
- || (node.inline ? wholeCardLink(!!href, () => inlineToTemplate(node.inline)) : "")
1650
+ || (node.inline ? wholeCardLink(whole, () => inlineToTemplate(node.inline)) : "")
1639
1651
  || (description ? escapeTemplate(description) : "");
1640
1652
  const descTag = blocks ? "div" : "p";
1641
1653
  // Icon renders inline before the title, a name or an image path, as in a card group
1642
1654
  const iconHtml = icon ? cardIconHtml(icon, title, ctx) : "";
1643
1655
  const titleHtml = title
1644
- ? `<${tag} class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</${tag}>`
1656
+ ? `<${tag} class="${CARD_TITLE_CLASS}" ${CARD_TITLE_ATTR}>${href && !whole ? `<a href="${escapeAttr(href)}">${escapeTemplate(title)}</a>` : escapeTemplate(title)}</${tag}>`
1645
1657
  : "";
1646
1658
  // A description and a body are both shown (the description used to be dropped when a body existed).
1647
1659
  const lead = blocks && description
@@ -1652,14 +1664,14 @@ function standaloneCardToAstro(node, ctx) {
1652
1664
  : "");
1653
1665
  // A tone (TONES), as on a card in a group (cardsToAstro)
1654
1666
  const toned = lookup(CARD_TONE_CLASSES, node.props?.tone);
1655
- const cardDefaultClass = href
1667
+ const cardDefaultClass = whole
1656
1668
  ? toned ? `p-4 ${toned} hover:shadow-md transition-colors` : "p-4 hover:border-foreground/20 transition-colors"
1657
1669
  : toned ? `p-4 ${toned}` : "p-4";
1658
1670
  const classAttr = mergeClassAttr(cardDefaultClass, node.props?.class);
1659
1671
  const passthrough = renderPassthroughAttrs(node.props, [
1660
1672
  "title", "href", "description", "icon", "variant", "class", "tone",
1661
1673
  ]);
1662
- if (href) {
1674
+ if (whole) {
1663
1675
  return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" data-variant="card" class="[text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1664
1676
  }
1665
1677
  return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n${iconHtml}\n${titleHtml}\n${desc}\n</CardContent>\n</Card>`;
package/dist/theme-css.js CHANGED
@@ -104,7 +104,7 @@ export function generateGlobalCss() {
104
104
  .docs-prose {
105
105
  line-height: 1.7;
106
106
 
107
- /* A heading that is part of a component (a card title: h3.not-prose) keeps the component look */
107
+ /* A heading inside a component (.not-prose) keeps the component look */
108
108
  & h1:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 2rem; font-weight: 700; margin-top: 0; margin-bottom: 1rem; line-height: 1.2; letter-spacing: -0.025em; }
109
109
  & h2:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 1.5rem; font-weight: 600; margin-top: 2.5rem; margin-bottom: 0.75rem; line-height: 1.3; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; letter-spacing: -0.015em; }
110
110
  & h3:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.5rem; line-height: 1.4; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/format-astro",
3
- "version": "0.2.0-beta.159",
3
+ "version": "0.2.0-beta.160",
4
4
  "description": "Astro format plugin for Dogsbay — export TreeNode[] to Astro component markup",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,13 +32,13 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "markdown-it": "^14.3.0",
35
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.159",
36
- "@dogsbay/render-html": "0.2.0-beta.159",
37
- "@dogsbay/icons": "0.2.0-beta.159",
38
- "@dogsbay/serialize-core": "0.2.0-beta.159",
39
- "@dogsbay/site-files": "0.2.0-beta.159",
40
- "@dogsbay/types": "0.2.0-beta.159",
41
- "@dogsbay/ui": "0.2.0-beta.159"
35
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.160",
36
+ "@dogsbay/render-html": "0.2.0-beta.160",
37
+ "@dogsbay/serialize-core": "0.2.0-beta.160",
38
+ "@dogsbay/site-files": "0.2.0-beta.160",
39
+ "@dogsbay/icons": "0.2.0-beta.160",
40
+ "@dogsbay/types": "0.2.0-beta.160",
41
+ "@dogsbay/ui": "0.2.0-beta.160"
42
42
  },
43
43
  "repository": {
44
44
  "type": "git",