@uniweb/kit 0.10.6 → 0.10.7

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/kit",
3
- "version": "0.10.6",
3
+ "version": "0.10.7",
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/semantic-parser": "1.2.0",
47
- "@uniweb/scene": "0.1.2"
46
+ "@uniweb/scene": "0.1.2",
47
+ "@uniweb/semantic-parser": "1.2.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": "^19.0.0",
@@ -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
@@ -136,6 +136,23 @@ export function Ref({ params, block }) {
136
136
  const missingTail = resolved.filter((r) => r.missing).map((r) => `[?${r.id}]`).join(', ')
137
137
  const body = [text, missingTail].filter(Boolean).join(', ')
138
138
 
139
+ // A reference to exactly one target links to it. The renderer emits the
140
+ // author's `{#id}` onto the labelled element, so `#fig-cells` is a real
141
+ // anchor in the page, in an EPUB, and in a Paged.js document.
142
+ //
143
+ // Only the single case. A cluster reads "Figures 1 and 2" — one href
144
+ // could only point at one of them, and linking the whole phrase to the
145
+ // first target is a worse answer than linking none of it. Making each
146
+ // number its own link means `renderGroupSameKind` returning nodes rather
147
+ // than a string; worth doing when someone needs it, not before.
148
+ if (onlyResolved.length === 1 && resolved.length === 1) {
149
+ return (
150
+ <a className="xref" href={`#${onlyResolved[0].id}`}>
151
+ {body}{locator}
152
+ </a>
153
+ )
154
+ }
155
+
139
156
  return <span className="xref">{body}{locator}</span>
140
157
  }
141
158