@uniweb/kit 0.10.6 → 0.10.8
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 +3 -3
- package/src/prose-tokens.css +21 -2
- package/src/styled/Render/index.jsx +21 -3
- package/src/xref/Ref.jsx +65 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.8",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"shiki": "^3.0.0",
|
|
44
44
|
"tailwind-merge": "^3.6.0",
|
|
45
45
|
"@uniweb/core": "0.8.0",
|
|
46
|
-
"@uniweb/
|
|
47
|
-
"@uniweb/
|
|
46
|
+
"@uniweb/scene": "0.1.2",
|
|
47
|
+
"@uniweb/semantic-parser": "1.2.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^19.0.0",
|
package/src/prose-tokens.css
CHANGED
|
@@ -97,8 +97,15 @@
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/* Anchors land clear of a fixed site header when something scrolls to them —
|
|
100
|
-
a contents rail, or a link with a #fragment.
|
|
101
|
-
|
|
100
|
+
a contents rail, or a link with a #fragment.
|
|
101
|
+
|
|
102
|
+
Figures, tables and display math are on this list because they became anchor
|
|
103
|
+
targets: an authored `{#fig-cells}` is emitted onto the element, and a
|
|
104
|
+
cross-reference links to it. Without them a `[#eq-binomial]` scrolls its
|
|
105
|
+
equation flush to the viewport top and the sticky header covers it — which is
|
|
106
|
+
exactly what happened the first time the feature was clicked. Headings had
|
|
107
|
+
the rule and nothing else did, because until then nothing else was a target. */
|
|
108
|
+
.prose :is(h1, h2, h3, h4, figure, table, .math-display) {
|
|
102
109
|
scroll-margin-top: calc(var(--header-height, 4rem) + 1rem);
|
|
103
110
|
}
|
|
104
111
|
|
|
@@ -129,3 +136,15 @@
|
|
|
129
136
|
.tml-sml-pad { padding-left: 0.05em; }
|
|
130
137
|
math mtd { padding-top: 0.5ex; padding-bottom: 0.5ex; }
|
|
131
138
|
math mtable.tml-jot mtd { padding-top: 0.7ex; padding-bottom: 0.7ex; }
|
|
139
|
+
|
|
140
|
+
/* AMS auto-numbering. Which equations number is the AUTHOR's choice, made in
|
|
141
|
+
LaTeX: `align` and `equation` number, `aligned` and the starred forms do not.
|
|
142
|
+
Without these two rules that choice was discarded -- `align` and `align*`
|
|
143
|
+
rendered identically, so an author who asked for numbers silently got none. */
|
|
144
|
+
.tml-eqn::before {
|
|
145
|
+
counter-increment: tmlEqnNo;
|
|
146
|
+
content: "(" counter(tmlEqnNo) ")";
|
|
147
|
+
}
|
|
148
|
+
body {
|
|
149
|
+
counter-reset: tmlEqnNo;
|
|
150
|
+
}
|
|
@@ -84,6 +84,23 @@ export const NOT_RENDERED = {
|
|
|
84
84
|
'document-group': 'an editor node — its documents reach content.links',
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The author's `{#id}`, if they wrote one.
|
|
89
|
+
*
|
|
90
|
+
* Reads the same two places the xref registry reads
|
|
91
|
+
* (`@uniweb/kit/xref/registry.js`), because an id that numbers a figure and an
|
|
92
|
+
* id that anchors it must be the same string — otherwise `[#fig-cells]` would
|
|
93
|
+
* resolve to "Figure 3" and link to nothing.
|
|
94
|
+
*
|
|
95
|
+
* Emitting it matters beyond cross-references: it is what makes
|
|
96
|
+
* `/page#fig-cells` land on the figure, and what gives EPUB and Paged.js real
|
|
97
|
+
* internal links. Until 2026-07-31 nothing emitted it, so an authored id
|
|
98
|
+
* existed only inside the registry and never reached the document.
|
|
99
|
+
*/
|
|
100
|
+
function authoredId(element) {
|
|
101
|
+
return element?.attrs?.id ?? element?.id ?? undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
/**
|
|
88
105
|
* Normalize whatever a caller passed into a sequence.
|
|
89
106
|
*
|
|
@@ -213,7 +230,7 @@ export function SequenceElement({ element, block, components }) {
|
|
|
213
230
|
// The id is the anchor `useHeadings()` and every in-page nav link
|
|
214
231
|
// resolve against, so it is behaviour rather than decoration.
|
|
215
232
|
return (
|
|
216
|
-
<Tag id={headingId(element.text || '')}>
|
|
233
|
+
<Tag id={authoredId(element) || headingId(element.text || '')}>
|
|
217
234
|
<SafeHtml value={element.text} as="span" />
|
|
218
235
|
</Tag>
|
|
219
236
|
)
|
|
@@ -235,7 +252,7 @@ export function SequenceElement({ element, block, components }) {
|
|
|
235
252
|
const { url, src, alt, caption, role } = element.attrs || {}
|
|
236
253
|
if (role === 'icon') return <Icon {...element.attrs} />
|
|
237
254
|
return (
|
|
238
|
-
<figure>
|
|
255
|
+
<figure id={authoredId(element)}>
|
|
239
256
|
<Image src={url || src} alt={alt || caption || ''} />
|
|
240
257
|
{caption && <figcaption>{caption}</figcaption>}
|
|
241
258
|
</figure>
|
|
@@ -262,6 +279,7 @@ export function SequenceElement({ element, block, components }) {
|
|
|
262
279
|
const Tag = display ? 'div' : 'span'
|
|
263
280
|
return (
|
|
264
281
|
<Tag
|
|
282
|
+
id={authoredId(element)}
|
|
265
283
|
className={display ? 'math-display' : 'math-inline'}
|
|
266
284
|
dangerouslySetInnerHTML={{ __html: element.mathml }}
|
|
267
285
|
/>
|
|
@@ -319,7 +337,7 @@ export function SequenceElement({ element, block, components }) {
|
|
|
319
337
|
// reference table on a phone.
|
|
320
338
|
return (
|
|
321
339
|
<div className="overflow-x-auto">
|
|
322
|
-
<table>
|
|
340
|
+
<table id={authoredId(element)}>
|
|
323
341
|
{hasHeader && <thead>{renderRow(first, 'h')}</thead>}
|
|
324
342
|
<tbody>{(hasHeader ? rest : rows).map((row, i) => renderRow(row, i))}</tbody>
|
|
325
343
|
</table>
|
package/src/xref/Ref.jsx
CHANGED
|
@@ -66,20 +66,39 @@ function renderEntry(entry, kindMeta) {
|
|
|
66
66
|
return label ? `${label}${sep}${counter}` : counter
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** A counter as a link to the element it numbers. */
|
|
70
|
+
function counterLink(entry) {
|
|
71
|
+
return (
|
|
72
|
+
<a key={entry.id} href={`#${entry.id}`}>
|
|
73
|
+
{entry.counterText}
|
|
74
|
+
</a>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Prose list punctuation: "1", "1 and 2", "1, 2, and 3".
|
|
80
|
+
*
|
|
81
|
+
* Takes and returns nodes rather than strings so each counter can be its own
|
|
82
|
+
* link. A cluster's LABEL is deliberately left outside them — "Figures" covers
|
|
83
|
+
* the whole group, and there is no single target it could point at.
|
|
84
|
+
*/
|
|
85
|
+
function joinProse(nodes) {
|
|
86
|
+
if (nodes.length <= 1) return nodes
|
|
87
|
+
if (nodes.length === 2) return [nodes[0], ' and ', nodes[1]]
|
|
88
|
+
const out = []
|
|
89
|
+
nodes.forEach((node, i) => {
|
|
90
|
+
if (i > 0) out.push(i === nodes.length - 1 ? ', and ' : ', ')
|
|
91
|
+
out.push(node)
|
|
92
|
+
})
|
|
93
|
+
return out
|
|
94
|
+
}
|
|
95
|
+
|
|
69
96
|
function renderGroupSameKind(entries, kindMeta) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
const label = kindMeta?.labelPlural || kindMeta?.label || ''
|
|
97
|
+
const plural = entries.length > 1
|
|
98
|
+
const label = (plural ? kindMeta?.labelPlural || kindMeta?.label : kindMeta?.label) || ''
|
|
74
99
|
const sep = kindMeta?.sep ?? ' '
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
if (counters.length === 2) {
|
|
78
|
-
body = counters.join(' and ')
|
|
79
|
-
} else {
|
|
80
|
-
body = counters.slice(0, -1).join(', ') + ', and ' + counters[counters.length - 1]
|
|
81
|
-
}
|
|
82
|
-
return label ? `${label}${sep}${body}` : body
|
|
100
|
+
const body = joinProse(entries.map(counterLink))
|
|
101
|
+
return label ? [label, sep, ...body] : body
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
export function Ref({ params, block }) {
|
|
@@ -119,24 +138,49 @@ export function Ref({ params, block }) {
|
|
|
119
138
|
`[xref] mixed-kind cluster (${[...new Set(allKinds)].join(', ')}) — falling back to comma-separated rendering`,
|
|
120
139
|
)
|
|
121
140
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
// Each kind keeps its own singular label, and each resolved part links
|
|
142
|
+
// to its own target. A missing one stays visible text.
|
|
143
|
+
const parts = []
|
|
144
|
+
resolved.forEach((r, i) => {
|
|
145
|
+
if (i > 0) parts.push(', ')
|
|
146
|
+
if (r.missing) {
|
|
147
|
+
parts.push(`[?${r.id}]`)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
const label = r.kindMeta?.label || ''
|
|
151
|
+
const sep = r.kindMeta?.sep ?? ' '
|
|
152
|
+
if (label) parts.push(label, sep)
|
|
153
|
+
parts.push(counterLink(r.entry))
|
|
154
|
+
})
|
|
155
|
+
return <span className="xref">{parts}{locator}</span>
|
|
126
156
|
}
|
|
127
157
|
|
|
128
158
|
const onlyResolved = resolved.filter((r) => !r.missing)
|
|
129
|
-
|
|
159
|
+
|
|
160
|
+
// The single case links the WHOLE reference — "Equation 1", label and all,
|
|
161
|
+
// is one phrase naming one thing, so the label belongs inside the link.
|
|
162
|
+
// A cluster cannot do that: "Figures 1 and 2" has one label over two
|
|
163
|
+
// targets, so there each counter is its own link and the label is plain.
|
|
164
|
+
if (onlyResolved.length === 1 && resolved.length === 1) {
|
|
165
|
+
const { entry, kindMeta } = onlyResolved[0]
|
|
166
|
+
return (
|
|
167
|
+
<a className="xref" href={`#${entry.id}`}>
|
|
168
|
+
{renderEntry(entry, kindMeta)}{locator}
|
|
169
|
+
</a>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const body = onlyResolved.length > 0
|
|
130
174
|
? renderGroupSameKind(
|
|
131
175
|
onlyResolved.map((r) => r.entry),
|
|
132
176
|
onlyResolved[0].kindMeta,
|
|
133
177
|
)
|
|
134
|
-
:
|
|
178
|
+
: []
|
|
135
179
|
|
|
136
|
-
const
|
|
137
|
-
const
|
|
180
|
+
const missing = resolved.filter((r) => r.missing).map((r) => `[?${r.id}]`)
|
|
181
|
+
const parts = missing.length > 0 && body.length > 0 ? [...body, ', ', ...missing] : [...body, ...missing]
|
|
138
182
|
|
|
139
|
-
return <span className="xref">{
|
|
183
|
+
return <span className="xref">{parts}{locator}</span>
|
|
140
184
|
}
|
|
141
185
|
|
|
142
186
|
export default Ref
|