@uniweb/kit 0.19.12 → 0.19.13

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.19.12",
3
+ "version": "0.19.13",
4
4
  "description": "Standard component library for Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -43,11 +43,11 @@
43
43
  "shiki": "^3.0.0",
44
44
  "tailwind-merge": "^3.6.0",
45
45
  "temml": "^0.13.2",
46
- "@uniweb/semantic-parser": "^1.5.0",
46
+ "@uniweb/semantic-parser": "^1.5.1",
47
47
  "@uniweb/core": "^0.35.0",
48
- "@uniweb/scene": "^0.1.3",
49
- "@uniweb/schemas": "^0.9.3",
50
- "@uniweb/projections": "^0.7.11"
48
+ "@uniweb/projections": "^0.7.11",
49
+ "@uniweb/schemas": "^0.9.4",
50
+ "@uniweb/scene": "^0.1.3"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": "^19.0.0",
@@ -325,12 +325,15 @@ export function SequenceElement({ element, block, components }) {
325
325
  // 2026-09-29 the parser delivered a document as an image, and this drew
326
326
  // `<img src="report.pdf">`.
327
327
  case 'document': {
328
- const { url, alt, caption, preview, author, description } = element.attrs || {}
329
- const name = caption || alt || fileNameOf(url)
328
+ // `file` is the file's own name — the parser reads it from the address, and a build
329
+ // that copies the file under a hashed name stamps the one it had; the address is
330
+ // the fallback for content parsed before documents carried one (2026-09-29).
331
+ const { url, alt, caption, preview, author, description, name: file } = element.attrs || {}
332
+ const name = caption || alt || file || fileNameOf(url)
330
333
  return (
331
334
  <figure id={authoredId(element)} data-kind="document">
332
335
  <Link to={url}>
333
- {preview ? <Image src={preview} alt="" /> : <FileLogo filename={url} />}
336
+ {preview ? <Image src={preview} alt="" /> : <FileLogo filename={file || url} />}
334
337
  <span>{name}</span>
335
338
  </Link>
336
339
  {(author || description) && (
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Visual Component
3
3
  *
4
- * Renders the first non-empty visual from the given candidates.
5
- * Resolution order: inset > video > image. Only tries what's passed.
4
+ * Renders one visual: a `content.media` item by its kind, or the first non-empty of
5
+ * the candidates it is handed (inset, then video, then image).
6
6
  *
7
7
  * @module @uniweb/kit/styled/Visual
8
8
  */
@@ -13,9 +13,19 @@ import { Image } from "../../components/Image/index.js";
13
13
  import { getChildBlockRenderer } from "../../utils/index.js";
14
14
 
15
15
  /**
16
- * Renders the first non-empty visual from the given candidates.
16
+ * Renders one visual.
17
+ *
18
+ * ⭐ `media` is a `content.media` item — an image, a video or an embedded component,
19
+ * tagged with its `kind` — rendered by that kind. Handed the whole `content.media`,
20
+ * it renders the first item: a slot of one is the first thing the author placed in
21
+ * it, whatever its kind. An embedded component needs `block`, whose insets it is
22
+ * one of.
23
+ *
24
+ * Without `media`, the first non-empty candidate: inset, then video, then image.
17
25
  *
18
26
  * @param {Object} props
27
+ * @param {Object|Object[]} [props.media] - A `content.media` item, or the list (its first item)
28
+ * @param {Object} [props.block] - The section's block — resolves an embedded component
19
29
  * @param {Object} [props.inset] - Inset Block instance (from block.insets or block.getInset())
20
30
  * @param {Object} [props.video] - Video object with src property
21
31
  * @param {Object} [props.image] - Image object with src and alt properties
@@ -23,24 +33,25 @@ import { getChildBlockRenderer } from "../../utils/index.js";
23
33
  * @param {React.ReactNode} [props.fallback] - Fallback when no visual is found
24
34
  *
25
35
  * @example
26
- * // Try inset first, fall back to video, then image
27
- * <Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} />
36
+ * // The slot of one — the author's first image, video or component
37
+ * <Visual media={content.media} block={block} className="rounded-lg" />
28
38
  *
29
39
  * @example
30
- * // Specific inset only
31
- * <Visual inset={block.getInset('chart')} className="rounded-lg" />
40
+ * // Every item of a gallery, in the author's order
41
+ * {content.media.map((item, i) => <Visual key={i} media={item} block={block} />)}
32
42
  *
33
43
  * @example
34
- * // Image only
35
- * <Visual image={content.images[1]} className="aspect-video" />
44
+ * // Try inset first, fall back to video, then image
45
+ * <Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} />
36
46
  */
37
- export function Visual({ inset, video, image, className, fallback = null }) {
38
- if (inset) {
39
- const Renderer = getChildBlockRenderer();
40
- const rendered = <Renderer blocks={[inset]} />;
41
- return className ? <div className={className}>{rendered}</div> : rendered;
47
+ export function Visual({ media, block, inset, video, image, className, fallback = null }) {
48
+ if (media !== undefined) {
49
+ const item = Array.isArray(media) ? media[0] : media;
50
+ return renderItem(item, block, className) ?? fallback;
42
51
  }
43
52
 
53
+ if (inset) return renderInset(inset, className);
54
+
44
55
  if (video) {
45
56
  return <Media {...video} className={className} />;
46
57
  }
@@ -52,3 +63,30 @@ export function Visual({ inset, video, image, className, fallback = null }) {
52
63
 
53
64
  return fallback;
54
65
  }
66
+
67
+ /** A `content.media` item, rendered by its kind — or null when it cannot be. */
68
+ function renderItem(item, block, className) {
69
+ if (!item || typeof item !== "object") return null;
70
+ const { kind, ...attrs } = item;
71
+ switch (kind) {
72
+ case "image": {
73
+ const { contentType, viewType, contentId, imgPos, ...imageProps } = attrs;
74
+ return <Image {...imageProps} className={className} />;
75
+ }
76
+ case "video":
77
+ return <Media {...attrs} className={className} />;
78
+ case "inset": {
79
+ const insetBlock = attrs.refId ? block?.getInset?.(attrs.refId) : null;
80
+ return insetBlock ? renderInset(insetBlock, className) : null;
81
+ }
82
+ default:
83
+ return null;
84
+ }
85
+ }
86
+
87
+ function renderInset(inset, className) {
88
+ const Renderer = getChildBlockRenderer();
89
+ if (!Renderer) return null;
90
+ const rendered = <Renderer blocks={[inset]} />;
91
+ return className ? <div className={className}>{rendered}</div> : rendered;
92
+ }