@uniweb/press 0.4.4 → 0.4.5

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.
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Page-chrome builders — page header, page footer, and page-number
3
+ * field codes.
4
+ *
5
+ * Stage 4 of kb/framework/plans/press-professional-docx.md.
6
+ *
7
+ * The underlying registration plumbing already exists in Press:
8
+ * `useDocumentOutput(block, 'docx', body, { role: 'header' })` lifts
9
+ * a fragment into the docx Section's header; `role: 'footer'` does the
10
+ * same for the footer. These builders are ergonomic wrappers — they
11
+ * give foundations a JSX shape that matches how Word documents are
12
+ * usually authored (a tree with PageHeader / PageFooter elements at
13
+ * the top level), and route the registration internally.
14
+ *
15
+ * Usage in a section component:
16
+ *
17
+ * import {
18
+ * useDocumentOutput,
19
+ * useDocumentOutputAsHeader,
20
+ * useDocumentOutputAsFooter,
21
+ * } from '@uniweb/press'
22
+ * import {
23
+ * PageHeader, PageFooter, PageNumber, TotalPages,
24
+ * } from '@uniweb/press/docx'
25
+ *
26
+ * function Cover({ block }) {
27
+ * useDocumentOutput(block, 'docx', body)
28
+ * useDocumentOutputAsHeader(block, 'docx',
29
+ * <PageHeader>
30
+ * <Paragraph data-alignment="right">
31
+ * <Image src="logo.png" />
32
+ * </Paragraph>
33
+ * </PageHeader>
34
+ * )
35
+ * useDocumentOutputAsFooter(block, 'docx',
36
+ * <PageFooter>
37
+ * <Paragraph data-alignment="center">
38
+ * <TextRun>Page </TextRun><PageNumber />
39
+ * <TextRun> of </TextRun><TotalPages />
40
+ * </Paragraph>
41
+ * </PageFooter>
42
+ * )
43
+ * }
44
+ *
45
+ * The PageHeader / PageFooter components are layout-transparent: they
46
+ * render their children inside an HTML container that the IR walker
47
+ * treats as a normal sequence of paragraphs. The role-based routing
48
+ * happens at the registration layer (useDocumentOutputAsHeader / Footer),
49
+ * not via element identity. This lets foundations build complex headers
50
+ * (multi-row layouts, brand blocks) without the IR walker needing a
51
+ * special "page header" type.
52
+ */
53
+
54
+ /**
55
+ * Wrapper element for a page header. Renders its children with no
56
+ * additional structure — used as a documentation/identity hint at the
57
+ * top of a header tree, and to give foundations a single JSX node to
58
+ * pass to `useDocumentOutputAsHeader`.
59
+ */
60
+ export function PageHeader({ children }) {
61
+ return <>{children}</>
62
+ }
63
+
64
+ /**
65
+ * Wrapper element for a page footer. Mirror of PageHeader.
66
+ */
67
+ export function PageFooter({ children }) {
68
+ return <>{children}</>
69
+ }
70
+
71
+ /**
72
+ * The current page number, as a Word field code that updates on
73
+ * print/preview. Inline element — usable inside any TextRun-context
74
+ * (Paragraph children, table cells, etc.).
75
+ *
76
+ * The IR walker has special-cased '_currentPage' as text content
77
+ * since pre-Press days; we emit a TextRun-shaped span carrying
78
+ * exactly that sentinel so the existing pipeline turns it into a
79
+ * <w:fldSimple> / <w:fldChar> page reference.
80
+ */
81
+ export function PageNumber() {
82
+ return <span data-type="text">_currentPage</span>
83
+ }
84
+
85
+ /**
86
+ * The total number of pages. Companion to PageNumber for "Page X of Y"
87
+ * footer patterns.
88
+ */
89
+ export function TotalPages() {
90
+ return <span data-type="text">_totalPages</span>
91
+ }
92
+
93
+ /**
94
+ * Common page-size presets, in twips. Foundations pass these to
95
+ * `compile('docx', { pageSize: pageSizes.A4 })` to override Word's
96
+ * default. The orientation field is set separately via the
97
+ * `pageOrientation` compile option (or by spreading + overriding).
98
+ *
99
+ * ISO A-series and US presets are exact. Other sizes available
100
+ * directly through the docx library if needed.
101
+ */
102
+ export const pageSizes = {
103
+ A4: { width: 11906, height: 16838 }, // 21.0 × 29.7 cm
104
+ A5: { width: 8419, height: 11906 }, // 14.8 × 21.0 cm
105
+ A3: { width: 16838, height: 23811 }, // 29.7 × 42.0 cm
106
+ LETTER: { width: 12240, height: 15840 }, // 8.5 × 11 in
107
+ LEGAL: { width: 12240, height: 20160 }, // 8.5 × 14 in
108
+ TABLOID: { width: 15840, height: 24480 }, // 11 × 17 in
109
+ }
@@ -11,26 +11,93 @@
11
11
  */
12
12
  import { parseStyledString } from './parseStyledString.js'
13
13
  import TextRun from './TextRun.jsx'
14
+ import Math from './Math.jsx'
15
+
16
+ /**
17
+ * Build the data-* attribute pairs for paragraph-level Stage-3 props.
18
+ * Returns a plain object that gets spread onto the rendered element.
19
+ */
20
+ function paragraphPolishProps({ tabStops, indent, role }) {
21
+ const out = {}
22
+ if (Array.isArray(tabStops) && tabStops.length) {
23
+ out['data-tab-stops'] = JSON.stringify(tabStops)
24
+ }
25
+ if (indent && typeof indent === 'object') {
26
+ // Lower-cased data-* keys to match the IR attribute map.
27
+ if (indent.left != null) out['data-indent-left'] = indent.left
28
+ if (indent.right != null) out['data-indent-right'] = indent.right
29
+ if (indent.firstLine != null) out['data-indent-firstline'] = indent.firstLine
30
+ if (indent.hanging != null) out['data-indent-hanging'] = indent.hanging
31
+ }
32
+ // Stage 6.0: <Paragraph role="Title"> attaches a named OOXML
33
+ // paragraph style. Resolution to actual font/size/color happens
34
+ // through theme.typography at compile time.
35
+ if (typeof role === 'string' && role.length) {
36
+ out['data-paragraph-style'] = role
37
+ }
38
+ return out
39
+ }
40
+
41
+ /**
42
+ * Block-level text container with optional styled string parsing.
43
+ *
44
+ * - Without `data`: renders children directly with data-type="paragraph".
45
+ * - With `data`: parses the HTML string for inline marks (<strong>, <em>,
46
+ * <u>) and hyperlinks (<a href="...">) and renders as styled children.
47
+ *
48
+ * @param {Object} props
49
+ * @param {React.ElementType} [props.as='p'] - Element to render.
50
+ * @param {string} [props.data] - HTML string with inline marks.
51
+ * @param {Array<{position:number, type?:string, leader?:string}>} [props.tabStops]
52
+ * Stage 3: paragraph-level tab stops. Position is in twips; type defaults
53
+ * to 'left'; leader is 'none' (default), 'dot', 'hyphen', 'underscore',
54
+ * 'middleDot'. Combine with `<Tab/>` (or `{'\t'}`) inside the paragraph
55
+ * children to align text on the stops.
56
+ * @param {{left?:number,right?:number,firstLine?:number,hanging?:number}} [props.indent]
57
+ * Stage 3: paragraph indentation, in twips.
58
+ */
59
+ export default function Paragraph({
60
+ as: Tag = 'p',
61
+ data,
62
+ tabStops,
63
+ indent,
64
+ role,
65
+ children,
66
+ ...props
67
+ }) {
68
+ const polish = paragraphPolishProps({ tabStops, indent, role })
14
69
 
15
- export default function Paragraph({ as: Tag = 'p', data, children, ...props }) {
16
70
  if (data) {
17
71
  const parts = parseStyledString(data)
18
72
 
19
73
  return (
20
- <Tag data-type="paragraph" {...props}>
21
- {parts.map((part, i) =>
22
- part.type === 'link' ? (
23
- <a
24
- key={i}
25
- data-type="externalHyperlink"
26
- data-link={part.href}
27
- href={part.href}
28
- >
29
- <span data-type="text" data-style="Hyperlink">
30
- {part.content}
31
- </span>
32
- </a>
33
- ) : (
74
+ <Tag data-type="paragraph" {...polish} {...props}>
75
+ {parts.map((part, i) => {
76
+ if (part.type === 'link') {
77
+ return (
78
+ <a
79
+ key={i}
80
+ data-type="externalHyperlink"
81
+ data-link={part.href}
82
+ href={part.href}
83
+ >
84
+ <span data-type="text" data-style="Hyperlink">
85
+ {part.content}
86
+ </span>
87
+ </a>
88
+ )
89
+ }
90
+ if (part.type === 'math') {
91
+ return (
92
+ <Math
93
+ key={i}
94
+ latex={part.latex}
95
+ display={part.display}
96
+ id={part.id}
97
+ />
98
+ )
99
+ }
100
+ return (
34
101
  <TextRun
35
102
  key={i}
36
103
  bold={part.bold}
@@ -40,18 +107,27 @@ export default function Paragraph({ as: Tag = 'p', data, children, ...props }) {
40
107
  {part.content}
41
108
  </TextRun>
42
109
  )
43
- )}
110
+ })}
44
111
  </Tag>
45
112
  )
46
113
  }
47
114
 
48
115
  return (
49
- <Tag data-type="paragraph" {...props}>
116
+ <Tag data-type="paragraph" {...polish} {...props}>
50
117
  {children}
51
118
  </Tag>
52
119
  )
53
120
  }
54
121
 
122
+ /**
123
+ * Inline tab marker. Emits a docx Tab element that interacts with the
124
+ * parent paragraph's tabStops. JSX-friendly alternative to writing
125
+ * `{'\t'}` literally (which React's whitespace handling can swallow).
126
+ */
127
+ export function Tab(props) {
128
+ return <span data-type="tab" {...props} />
129
+ }
130
+
55
131
  /**
56
132
  * Render an array of paragraph strings. Convenience wrapper used by
57
133
  * the default Section component pattern.
@@ -1,11 +1,7 @@
1
1
  /**
2
2
  * Table, Tr, Td — ergonomic wrappers for Press's table vocabulary.
3
3
  *
4
- * Foundations that built tables before this existed hand-rolled <div>s
5
- * with data-type="table"/"tableRow"/"tableCell" attributes plus width,
6
- * margin, and border data attributes per cell. These components ship
7
- * sensible defaults and a shared column-widths channel, so the common
8
- * case reduces to:
4
+ * Common case (percent column widths, soft borders):
9
5
  *
10
6
  * <Table widths={[15, 60, 25]}>
11
7
  * <Tr header>
@@ -20,6 +16,20 @@
20
16
  * </Tr>
21
17
  * </Table>
22
18
  *
19
+ * Stage 1 of press-professional-docx adds:
20
+ *
21
+ * - <Table columnWidths={[cm(15), cm(4), cm(1.5), cm(4)]} layout="fixed">
22
+ * fixed-width columns specified in twips (use the unit helpers).
23
+ * - <Table width={{ size: 100, type: 'pct' }}>
24
+ * whole-table width.
25
+ * - <Table borders={{ top: { style: 'single', size: 4, color: 'cccccc' }, … }}>
26
+ * table-default borders. Per-cell borders still win.
27
+ * - <Td shading="4775b2"> or <Td shading={{ fill: '4775b2', type: 'clear' }}>
28
+ * cell background color.
29
+ * - <Td valign="center"> top | center | bottom.
30
+ * - <Tr header> now also marks the row as a docx tableHeader, so it
31
+ * repeats at the top of each new page when the table breaks.
32
+ *
23
33
  * Every data-* attribute set by default can be overridden by passing the
24
34
  * same attribute on the Td — explicit spreads win. String children are
25
35
  * wrapped in a Press <Paragraph> automatically; complex cells (multiple
@@ -38,6 +48,26 @@ import {
38
48
  } from 'react'
39
49
  import Paragraph from './Paragraph.jsx'
40
50
  import TextRun from './TextRun.jsx'
51
+ import { useDocumentTheme, resolveThemeColor } from '../ThemeContext.js'
52
+
53
+ /**
54
+ * Run a borders object through theme color resolution. Each side's
55
+ * `color` key may be a hex literal or a theme key like 'softBorder'.
56
+ */
57
+ function resolveBordersTheme(borders, theme) {
58
+ if (!borders) return borders
59
+ const out = {}
60
+ for (const [side, sideProps] of Object.entries(borders)) {
61
+ if (!sideProps) continue
62
+ out[side] = {
63
+ ...sideProps,
64
+ ...(sideProps.color
65
+ ? { color: resolveThemeColor(sideProps.color, theme) }
66
+ : {}),
67
+ }
68
+ }
69
+ return out
70
+ }
41
71
 
42
72
  const TableCtx = createContext({ widths: null, borderColor: 'cccccc' })
43
73
 
@@ -45,20 +75,83 @@ const TableCtx = createContext({ widths: null, borderColor: 'cccccc' })
45
75
  * Table — the outer wrapper.
46
76
  *
47
77
  * @param {Object} props
48
- * @param {number[]} [props.widths] - Column widths in percent; each Td reads its
49
- * width by column index. Omit to let every cell declare its own width.
50
- * @param {string} [props.borderColor='cccccc'] - Hex (no #) for cell borders.
78
+ * @param {number[]} [props.widths] - Column widths in percent. Each Td reads
79
+ * its width by column index. Cell-level widths are emitted via
80
+ * `data-width-size`/`data-width-type=pct`.
81
+ * @param {number[]} [props.columnWidths] - Column widths in twips. Sets
82
+ * docx `columnWidths` for layout-aware sizing. Use the unit helpers
83
+ * (cm, mm, inch, pt) to express them naturally. When set, `layout`
84
+ * defaults to 'fixed' so Word doesn't redistribute columns.
85
+ * @param {'fixed'|'autofit'} [props.layout] - Table layout type. 'fixed'
86
+ * honors columnWidths exactly; 'autofit' lets Word redistribute to
87
+ * fit content. Defaults to 'fixed' when columnWidths is provided.
88
+ * @param {{size: number, type?: string}} [props.width] - Whole-table width.
89
+ * { size, type } where type is 'pct' (percent), 'dxa' (twips), or 'auto'.
90
+ * @param {Object} [props.borders] - Table-default borders. Same shape as
91
+ * per-cell borders: { top, bottom, left, right, insideHorizontal,
92
+ * insideVertical } each with { style, size, color }.
93
+ * @param {string} [props.borderColor='cccccc'] - Hex (no #) for cell-level
94
+ * default borders. Used by Td when no per-cell borders are set.
51
95
  */
52
96
  export function Table({
53
97
  widths,
98
+ columnWidths,
99
+ layout,
100
+ width,
101
+ borders,
54
102
  borderColor = 'cccccc',
55
103
  className,
56
104
  children,
57
105
  ...props
58
106
  }) {
107
+ const theme = useDocumentTheme()
108
+ const tableAttrs = {}
109
+
110
+ if (columnWidths && columnWidths.length) {
111
+ tableAttrs['data-table-column-widths'] = columnWidths.join(',')
112
+ // Default to fixed when the foundation gave us widths — Word
113
+ // otherwise redistributes them. Honor an explicit `layout` prop.
114
+ tableAttrs['data-table-layout'] = layout || 'fixed'
115
+ } else if (layout) {
116
+ tableAttrs['data-table-layout'] = layout
117
+ }
118
+
119
+ if (width) {
120
+ if (width.size != null) tableAttrs['data-table-width-size'] = width.size
121
+ tableAttrs['data-table-width-type'] = width.type ?? 'pct'
122
+ }
123
+
124
+ const resolvedBorders = resolveBordersTheme(borders, theme)
125
+ if (resolvedBorders) {
126
+ for (const [side, sideProps] of Object.entries(resolvedBorders)) {
127
+ if (!sideProps) continue
128
+ // The IR map uses `insideh`/`insidev` (single token) to keep
129
+ // attribute names parser-friendly. Translate from camelCase.
130
+ const sideKey =
131
+ side === 'insideHorizontal'
132
+ ? 'insideh'
133
+ : side === 'insideVertical'
134
+ ? 'insidev'
135
+ : side
136
+ if (sideProps.style)
137
+ tableAttrs[`data-table-borders-${sideKey}-style`] = sideProps.style
138
+ if (sideProps.size != null)
139
+ tableAttrs[`data-table-borders-${sideKey}-size`] = sideProps.size
140
+ if (sideProps.color)
141
+ tableAttrs[`data-table-borders-${sideKey}-color`] = sideProps.color
142
+ }
143
+ }
144
+
145
+ // Resolve borderColor against the theme too — it's the per-cell
146
+ // default border color used when a Td doesn't override.
147
+ const resolvedBorderColor =
148
+ resolveThemeColor(borderColor, theme) ?? borderColor
149
+
59
150
  return (
60
- <TableCtx.Provider value={{ widths, borderColor }}>
61
- <div data-type="table" className={className} {...props}>
151
+ <TableCtx.Provider
152
+ value={{ widths, borderColor: resolvedBorderColor, theme }}
153
+ >
154
+ <div data-type="table" className={className} {...tableAttrs} {...props}>
62
155
  {children}
63
156
  </div>
64
157
  </TableCtx.Provider>
@@ -72,20 +165,34 @@ export function Table({
72
165
  * cell can look up its width from the Table context, and to mark cells
73
166
  * in a `header` row for the default emphasis + heavier bottom border.
74
167
  *
168
+ * Column-index assignment is span-aware: a <Td colSpan={3}> takes
169
+ * three column slots, so the next cell is at index 3, not 1. Without
170
+ * this, a row with a colSpan'd cell followed by a per-column-width
171
+ * cell would pull the wrong width from `widths={…}`.
172
+ *
75
173
  * @param {Object} props
76
- * @param {boolean} [props.header=false] - Bolds text and thickens the border.
174
+ * @param {boolean} [props.header=false] - Bolds text, thickens the bottom
175
+ * border, and marks the row as a docx tableHeader (it repeats at the
176
+ * top of each new page when the table breaks across pages).
77
177
  */
78
178
  export function Tr({ header = false, className, children, ...props }) {
79
- const cells = Children.toArray(children).map((child, idx) =>
80
- isValidElement(child)
81
- ? cloneElement(child, {
82
- _col: child.props._col ?? idx,
83
- _header: child.props._header ?? header,
84
- })
85
- : child,
86
- )
179
+ let nextCol = 0
180
+ const cells = Children.toArray(children).map((child) => {
181
+ if (!isValidElement(child)) return child
182
+ const col = child.props._col ?? nextCol
183
+ const span =
184
+ typeof child.props.colSpan === 'number' && child.props.colSpan > 1
185
+ ? child.props.colSpan
186
+ : 1
187
+ nextCol = col + span
188
+ return cloneElement(child, {
189
+ _col: col,
190
+ _header: child.props._header ?? header,
191
+ })
192
+ })
193
+ const rowAttrs = header ? { 'data-row-header': '' } : {}
87
194
  return (
88
- <div data-type="tableRow" className={className} {...props}>
195
+ <div data-type="tableRow" className={className} {...rowAttrs} {...props}>
89
196
  {cells}
90
197
  </div>
91
198
  )
@@ -108,6 +215,12 @@ const CELL_PAD = { top: 80, bottom: 80, left: 120, right: 120 }
108
215
  * @param {number} [props.width] - Column width in percent (overrides Table.widths).
109
216
  * @param {boolean} [props.emphasis=false] - Force bold regardless of header state.
110
217
  * @param {'single'|'double'|'none'|string} [props.borderBottom] - Bottom border style.
218
+ * @param {string|{fill: string, type?: string, color?: string}} [props.shading] -
219
+ * Cell background fill. String shorthand is the hex color (no '#'),
220
+ * defaulting to a solid fill. Object form lets you set the OOXML
221
+ * shading type ('clear' default = plain fill, 'pct25' / 'horzStripe' / …
222
+ * for patterns).
223
+ * @param {'top'|'center'|'bottom'} [props.valign] - Vertical alignment of cell content.
111
224
  */
112
225
  export function Td({
113
226
  _col = 0,
@@ -115,12 +228,16 @@ export function Td({
115
228
  width,
116
229
  emphasis = false,
117
230
  borderBottom,
231
+ shading,
232
+ valign,
233
+ colSpan,
234
+ rowSpan,
118
235
  className,
119
236
  style,
120
237
  children,
121
238
  ...rest
122
239
  }) {
123
- const { widths, borderColor } = useContext(TableCtx)
240
+ const { widths, borderColor, theme } = useContext(TableCtx)
124
241
  const colWidth = width ?? widths?.[_col]
125
242
 
126
243
  const defaults = {
@@ -140,6 +257,24 @@ export function Td({
140
257
  defaults['data-width-size'] = colWidth
141
258
  defaults['data-width-type'] = 'pct'
142
259
  }
260
+ if (shading) {
261
+ const s =
262
+ typeof shading === 'string' ? { fill: shading } : shading
263
+ const resolvedFill = resolveThemeColor(s.fill, theme)
264
+ const resolvedColor = resolveThemeColor(s.color, theme)
265
+ if (resolvedFill) defaults['data-shading-fill'] = resolvedFill
266
+ if (s.type) defaults['data-shading-type'] = s.type
267
+ if (resolvedColor) defaults['data-shading-color'] = resolvedColor
268
+ }
269
+ if (valign) {
270
+ defaults['data-valign'] = valign
271
+ }
272
+ if (typeof colSpan === 'number' && colSpan > 1) {
273
+ defaults['data-grid-span'] = colSpan
274
+ }
275
+ if (typeof rowSpan === 'number' && rowSpan > 1) {
276
+ defaults['data-row-span'] = rowSpan
277
+ }
143
278
 
144
279
  const flexStyle =
145
280
  colWidth != null
@@ -1,19 +1,98 @@
1
+ import {
2
+ useDocumentTheme,
3
+ resolveThemeColor,
4
+ resolveThemeFont,
5
+ } from '../ThemeContext.js'
6
+
7
+ /**
8
+ * Stage 6.1 safety net. If a Date object reaches a TextRun as a
9
+ * child, coerce it to ISO YYYY-MM-DD before React's default
10
+ * stringification produces "Sat Feb 28 2026 19:00:00 GMT-0500 …".
11
+ *
12
+ * The right way to render a date is via `<DateText value={…} format="…"/>`
13
+ * from @uniweb/press/format, which gives the foundation control over
14
+ * locale and format. This safety net is the floor: a careless
15
+ * `<TextRun>{date}</TextRun>` produces a readable date instead of a
16
+ * garbage timezone string — and avoids the React 19 "Objects are not
17
+ * valid as a React child (found: Invalid Date)" runtime error.
18
+ *
19
+ * Walks one level of direct children. We can't use React.Children.map
20
+ * here because it rejects Date objects upfront (only plain objects
21
+ * are special-cased; Date counts as "object").
22
+ */
23
+ function coerceDate(child) {
24
+ if (!(child instanceof Date)) return child
25
+ if (Number.isNaN(child.getTime())) return ''
26
+ const y = child.getUTCFullYear()
27
+ const m = String(child.getUTCMonth() + 1).padStart(2, '0')
28
+ const d = String(child.getUTCDate()).padStart(2, '0')
29
+ return `${y}-${m}-${d}`
30
+ }
31
+
32
+ function coerceDateChildren(children) {
33
+ if (children == null) return children
34
+ if (Array.isArray(children)) return children.map(coerceDate)
35
+ return coerceDate(children)
36
+ }
37
+
1
38
  /**
2
39
  * Inline text span. Renders <span data-type="text"> with optional
3
- * bold/italic/underline via data attributes.
40
+ * bold/italic/underline + color/size/font via data attributes.
4
41
  *
5
42
  * Maps to the `text` IR node type → docx TextRun.
43
+ *
44
+ * @param {Object} props
45
+ * @param {boolean} [props.bold]
46
+ * @param {boolean} [props.italics]
47
+ * @param {boolean} [props.underline]
48
+ * @param {string} [props.color] - Hex color (with or without '#') or a
49
+ * theme key ('accent', 'body', 'muted', 'softBorder'). Theme keys
50
+ * resolve via the active <DocumentProvider theme={…}>; literals
51
+ * pass through after stripping any leading '#'.
52
+ * @param {number} [props.size] - Font size in half-points (so 28pt = 56).
53
+ * Use the convertPointsToHalfPoints helper to keep the doubling
54
+ * intent visible in foundation code.
55
+ * @param {string} [props.font] - Font family name (e.g. 'Calibri') or a
56
+ * theme key ('body', 'heading', 'mono').
57
+ * @param {string} [props.style] - Named character/paragraph style.
6
58
  */
7
- export default function TextRun({ children, bold, italics, underline, style, ...props }) {
59
+ export default function TextRun({
60
+ children,
61
+ bold,
62
+ italics,
63
+ underline,
64
+ color,
65
+ size,
66
+ font,
67
+ smallCaps,
68
+ allCaps,
69
+ strike,
70
+ style,
71
+ role,
72
+ ...props
73
+ }) {
74
+ const theme = useDocumentTheme()
75
+ const resolvedColor = resolveThemeColor(color, theme)
76
+ const resolvedFont = resolveThemeFont(font, theme)
77
+ // Stage 6.0: `role="Label"` references a named OOXML character
78
+ // style. Maps to `data-style` (the existing run-style channel).
79
+ // Inline overrides (color, size, bold, …) win over the style.
80
+ const resolvedStyle = style ?? role
8
81
  const dataProps = { 'data-type': 'text' }
9
82
  if (bold) dataProps['data-bold'] = 'true'
10
83
  if (italics) dataProps['data-italics'] = 'true'
11
84
  if (underline) dataProps['data-underline'] = 'true'
12
- if (style) dataProps['data-style'] = style
85
+ if (resolvedColor) dataProps['data-color'] = resolvedColor
86
+ if (size != null) dataProps['data-size'] = size
87
+ if (resolvedFont) dataProps['data-font'] = resolvedFont
88
+ if (smallCaps) dataProps['data-smallcaps'] = 'true'
89
+ if (allCaps) dataProps['data-allcaps'] = 'true'
90
+ if (strike) dataProps['data-strike'] = 'true'
91
+ if (resolvedStyle) dataProps['data-style'] = resolvedStyle
13
92
 
14
93
  return (
15
94
  <span {...dataProps} {...props}>
16
- {children}
95
+ {coerceDateChildren(children)}
17
96
  </span>
18
97
  )
19
98
  }
package/src/docx/index.js CHANGED
@@ -11,10 +11,12 @@
11
11
  * '@uniweb/press/docx' does not pull in the ~3.4 MB docx library.
12
12
  */
13
13
 
14
- export { default as Paragraph, Paragraphs } from './Paragraph.jsx'
14
+ export { default as Paragraph, Paragraphs, Tab } from './Paragraph.jsx'
15
15
  export { default as TextRun } from './TextRun.jsx'
16
16
  export { H1, H2, H3, H4 } from './Headings.jsx'
17
17
  export { default as Image, Images } from './Image.jsx'
18
+ export { default as BrandLogo } from './BrandLogo.jsx'
19
+ export { default as Math } from './Math.jsx'
18
20
  export { default as Caption } from './Caption.jsx'
19
21
  export { default as Figure } from './Figure.jsx'
20
22
  export { Table, Tr, Td } from './Table.jsx'
@@ -24,10 +26,21 @@ export { BulletList, NumberedList } from './Lists.jsx'
24
26
  export { default as TableOfContents } from './TableOfContents.jsx'
25
27
  export { default as FootnoteReference } from './FootnoteReference.jsx'
26
28
  export { default as WebOnly } from './WebOnly.jsx'
29
+ export {
30
+ PageHeader,
31
+ PageFooter,
32
+ PageNumber,
33
+ TotalPages,
34
+ pageSizes,
35
+ } from './PageChrome.jsx'
27
36
 
28
37
  export {
29
38
  convertMillimetersToTwip,
30
39
  convertCentimetersToTwip,
31
40
  convertInchesToTwip,
32
41
  convertPointsToHalfPoints,
42
+ cm,
43
+ mm,
44
+ inch,
45
+ pt,
33
46
  } from './units.js'