@uniweb/press 0.4.8 → 0.4.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/press",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "description": "Press your React components into downloadable files — Word, Excel, PDF. Registration-based pattern with lazy-loaded format adapters.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -510,6 +510,9 @@ function irToInlineChildren(node) {
510
510
  // a TextRun so the OOXML parent is <w:r>.
511
511
  return [new TextRun({ children: [new DocxTab()] })]
512
512
  case 'externalHyperlink':
513
+ // A plain <a href> from same-source JSX — same destination, same
514
+ // emitter. See the href note in ir/parser.js.
515
+ case 'a':
513
516
  return [irToExternalHyperlink(node)]
514
517
  case 'internalHyperlink':
515
518
  return [irToInternalHyperlink(node)]
@@ -612,10 +615,13 @@ function irToTextRunPair(node) {
612
615
  }
613
616
 
614
617
  function irToExternalHyperlink(node) {
618
+ // `link` is the data-* spelling (data-link); `href` arrives from a plain
619
+ // <a href>. Same destination either way.
620
+ const destination = node.link || node.href || ''
615
621
  const children = (node.children || []).flatMap(irToInlineChildren)
616
622
  return new ExternalHyperlink({
617
- children: children.length ? children : [new TextRun({ text: node.link || '' })],
618
- link: node.link || '',
623
+ children: children.length ? children : [new TextRun({ text: destination })],
624
+ link: destination,
619
625
  })
620
626
  }
621
627
 
@@ -31,6 +31,7 @@
31
31
  import JSZip from 'jszip'
32
32
  import { parseFragment } from 'parse5'
33
33
  import { fetchAssets } from '../assets/fetch.js'
34
+ import { MATH_CSS } from './math-css.js'
34
35
 
35
36
  // ============================================================================
36
37
  // Public API
@@ -625,4 +626,5 @@ pre {
625
626
  overflow: auto;
626
627
  white-space: pre-wrap;
627
628
  }
629
+ ${MATH_CSS}
628
630
  `
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview The CSS a Temml-produced MathML document needs to lay out.
3
+ *
4
+ * Math arrives as MathML, which browsers render natively — so it is tempting
5
+ * to conclude, as this pipeline's own comments once did, that it carries no
6
+ * CSS dependency at all. It does, for two separate reasons:
7
+ *
8
+ * 1. **Temml emits class hooks and expects a stylesheet to define them.**
9
+ * `tml-left` / `tml-right` carry the `&` column alignment of an `aligned`
10
+ * environment, and `tml-jot` carries its extra row spacing. With nothing
11
+ * defining them, a derivation's `=` signs do not line up and its rows sit
12
+ * flush against each other. The declarations below are Temml's own, from
13
+ * `Temml-Local.css`.
14
+ *
15
+ * 2. **`mtd` gets no vertical padding from the browser.** Temml's stylesheet
16
+ * only *adjusts* row spacing for jot and small, on the stated assumption
17
+ * that "default mtd top padding is 0.5ex per MathML-Core and user-agent
18
+ * CSS". Measured in Chrome 2026-07, a pristine `mtd` gets 0px — so a
19
+ * `pmatrix` or `cases`, which carries no `tml-*` class at all, renders with
20
+ * its rows touching. Shipping Temml's stylesheet verbatim fixes the
21
+ * derivation and leaves the matrix broken; the `math mtd` rule below is the
22
+ * part Temml does not have.
23
+ *
24
+ * ── Why this is a string and not an imported stylesheet ──
25
+ *
26
+ * These adapters run in browser hosts as well as Node ones (see the host-
27
+ * supplied asset loaders in `assets/fetch.js`), so press cannot read a file at
28
+ * runtime, and it does not depend on `@uniweb/kit`. The same declarations
29
+ * therefore live in `kit/src/prose-tokens.css` for the browser lane.
30
+ *
31
+ * That is a duplicate, and duplicates drift — so it is pinned mechanically
32
+ * rather than by comment: `framework/_contracts/math-css-parity.test.js`
33
+ * parses both and fails when they disagree. Change one, change the other, or
34
+ * the contract test names it.
35
+ */
36
+
37
+ export const MATH_CSS = `/* Temml's own class hooks — without these an aligned environment neither
38
+ aligns nor breathes. */
39
+ .tml-right { text-align: right; }
40
+ .tml-left { text-align: left; }
41
+ .tml-sml-pad { padding-left: 0.05em; }
42
+ /* Row spacing. The browser gives mtd no vertical padding, so state the
43
+ MathML-Core default here; Temml's own CSS only adjusts it for jot. */
44
+ math mtd { padding-top: 0.5ex; padding-bottom: 0.5ex; }
45
+ math mtable.tml-jot mtd { padding-top: 0.7ex; padding-bottom: 0.7ex; }`
@@ -231,6 +231,8 @@ export const DEFAULT_POLYFILL_URL =
231
231
  * book PDF without a custom foundation stylesheet. Foundations override by
232
232
  * passing `options.stylesheet`.
233
233
  */
234
+ import { MATH_CSS } from './math-css.js'
235
+
234
236
  export const DEFAULT_STYLESHEET = `
235
237
  /* Hidden metadata block — the DOM carries it for CSS string-set / string() */
236
238
  [data-pagedjs-metadata] { display: none; }
@@ -292,4 +294,5 @@ blockquote {
292
294
  }
293
295
  ul, ol { margin: 0.5em 0 0.75em 1.5em; }
294
296
  figure { break-inside: avoid; }
297
+ ${MATH_CSS}
295
298
  `
@@ -374,6 +374,7 @@ function blockNodeToTypst(node) {
374
374
  return inlineNodeToTypst(node)
375
375
 
376
376
  case 'link':
377
+ case 'a':
377
378
  return inlineNodeToTypst(node)
378
379
 
379
380
  case 'math':
@@ -526,7 +527,7 @@ function inlineNodeToTypst(node) {
526
527
  return text
527
528
  }
528
529
 
529
- if (node.type === 'link') {
530
+ if (node.type === 'link' || node.type === 'a') {
530
531
  const href = node.href || ''
531
532
  const inner = inlineChildrenToTypst(node.children || [])
532
533
  return `#link(${quoteTypstString(href)})[${inner}]`
package/src/ir/parser.js CHANGED
@@ -103,6 +103,22 @@ function nodeToIR(node) {
103
103
  const properties = attributesToProperties(node.attrs || [])
104
104
  const children = collectChildren(node)
105
105
 
106
+ // A plain <a href> carries its destination in a normal HTML attribute, not
107
+ // a data-* one, so attributesToProperties drops it. Foundations reach this
108
+ // shape by writing an ordinary anchor in JSX that serves both the browser
109
+ // preview and the compile input — the same-source property principles.md
110
+ // §8 asks us to pursue. Without the href the adapters still emit the link
111
+ // TEXT and silently lose the destination, which is precisely the
112
+ // preview/output drift that principle exists to eliminate.
113
+ //
114
+ // Only `href` is lifted, and only for an anchor: it is the one plain
115
+ // attribute whose document meaning is unambiguous. The IR stays data-*
116
+ // driven everywhere else.
117
+ if (type === 'a' && properties.href === undefined) {
118
+ const href = getAttr(node, 'href')
119
+ if (href) properties.href = href
120
+ }
121
+
106
122
  const obj = { type, ...properties }
107
123
 
108
124
  if (type === 'text') {