@theoremjs/react 0.3.4-next.598.gb003e90 → 0.3.4-next.600.gb15be9a

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": "@theoremjs/react",
3
- "version": "0.3.4-next.598.gb003e90",
3
+ "version": "0.3.4-next.600.gb15be9a",
4
4
  "description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -60,8 +60,8 @@
60
60
  "@stylexjs/stylex": "0.19.0",
61
61
  "react": "^19.0.0",
62
62
  "react-dom": "^19.0.0",
63
- "@theoremjs/agents": "^0.3.4-next.598.gb003e90",
64
- "@theoremjs/web": "^0.3.4-next.598.gb003e90"
63
+ "@theoremjs/agents": "^0.3.4-next.600.gb15be9a",
64
+ "@theoremjs/web": "^0.3.4-next.600.gb15be9a"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@astryxdesign/core": {
package/ui/ShapedData.js CHANGED
@@ -3,6 +3,7 @@ import { CodeBlock } from '@astryxdesign/core/CodeBlock';
3
3
  import { Collapsible, CollapsibleGroup } from '@astryxdesign/core/Collapsible';
4
4
  import { HStack } from '@astryxdesign/core/HStack';
5
5
  import { useLocale } from '@astryxdesign/core/i18n';
6
+ import { useLightbox } from '@astryxdesign/core/Lightbox';
6
7
  import { Link } from '@astryxdesign/core/Link';
7
8
  import { List, ListItem } from '@astryxdesign/core/List';
8
9
  import { Markdown } from '@astryxdesign/core/Markdown';
@@ -10,14 +11,16 @@ import { MetadataList, MetadataListItem } from '@astryxdesign/core/MetadataList'
10
11
  import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
11
12
  import { Table } from '@astryxdesign/core/Table';
12
13
  import { Text } from '@astryxdesign/core/Text';
14
+ import { Thumbnail } from '@astryxdesign/core/Thumbnail';
13
15
  import { Token } from '@astryxdesign/core/Token';
14
16
  import { VStack } from '@astryxdesign/core/VStack';
15
- import { fieldLabel, fieldReading, isPlain, isProse, isRow, json, MAX_ROWS, plainReading, rowHeading, shapeOf, splitFields, unpacked, withUnit, } from '@theoremjs/web/client';
17
+ import { fieldLabel, fieldReading, isPlain, isProse, isRow, json, layoutChars, MAX_ROWS, plainReading, rowHeading, shapeOf, splitFields, unpacked, withUnit, } from '@theoremjs/web/client';
16
18
  import { Component, useMemo, useState } from 'react';
17
19
  import { Arrive, useArrive } from "./arrive.js";
18
20
  import { useLabels } from "./labels-provider.js";
19
21
  import { useMarkdownPlugins } from "./markdown-plugins.js";
20
22
  import { keyedByContent } from "./row-keys.js";
23
+ import { VoiceNote } from "./VoiceNote.js";
21
24
  /** Each way a plain value reads, drawn. */
22
25
  function ProseReading({ text }) {
23
26
  return (_jsx(Markdown, { density: "compact", headingLevelStart: 4, plugins: useMarkdownPlugins(), children: text }));
@@ -128,6 +131,45 @@ function Fields({ row, depth, units }) {
128
131
  // why: The top level's first section starts open: it's usually the payload. The rest wait to be asked for.
129
132
  defaultValue: depth === 0 ? sections.slice(0, 1).map((shown) => shown.key) : [], children: sections.map((shown) => depth === 0 ? (_jsx(ArrivingFieldSection, { shown: shown, depth: depth }, shown.key)) : (_jsx(FieldSection, { shown: shown, depth: depth }, shown.key))) })), depth === 0 && fields ? _jsx(Arrive, { children: fields }) : null] }));
130
133
  }
134
+ /** A media part's source: its base64 `data` as a data URL, or its `uri`. */
135
+ function partSrc(part) {
136
+ return 'uri' in part ? part.uri : `data:${part.mimeType};base64,${part.data}`;
137
+ }
138
+ function viewable(part) {
139
+ return part.type === 'image' || part.type === 'video';
140
+ }
141
+ /** A list of parts in its own order; every image and video opens in one Lightbox, so the arrows walk them all. */
142
+ function Parts({ parts }) {
143
+ const t = useLabels();
144
+ const shown = parts.slice(0, MAX_ROWS);
145
+ const media = [];
146
+ /** Each viewable part's place in the Lightbox, by its place in the list. */
147
+ const slides = new Map();
148
+ shown.forEach((part, index) => {
149
+ if (!viewable(part))
150
+ return;
151
+ slides.set(index, media.length);
152
+ const alt = t(part.type === 'image' ? '@theorem.data.image' : '@theorem.data.video', {
153
+ index: String(media.length + 1),
154
+ });
155
+ media.push({ src: partSrc(part), alt, type: part.type });
156
+ });
157
+ const lightbox = useLightbox({ media, hasZoom: true });
158
+ return (_jsxs(VStack, { gap: 2, children: [shown.map((part, index) => {
159
+ const key = String(index);
160
+ if (part.type === 'text')
161
+ return _jsx(ProseReading, { text: part.text }, key);
162
+ const slide = slides.get(index);
163
+ const item = slide === undefined ? undefined : media[slide];
164
+ if (slide !== undefined && item) {
165
+ return (_jsx(Thumbnail, { src: part.type === 'image' ? item.src : undefined, alt: item.alt, label: item.alt, onClick: () => lightbox.open(slide) }, key));
166
+ }
167
+ if (part.type === 'audio') {
168
+ return _jsx(VoiceNote, { src: partSrc(part), mimeType: part.mimeType }, key);
169
+ }
170
+ return _jsx(Token, { label: part.mimeType, size: "sm" }, key);
171
+ }), _jsx(More, { total: parts.length }), lightbox.element] }));
172
+ }
131
173
  /** Each shape a value takes, drawn. */
132
174
  const SHAPES = {
133
175
  json: (_, { value }) => _jsx(Json, { text: json(value) }),
@@ -138,6 +180,7 @@ const SHAPES = {
138
180
  table: ({ columns, rows }, { units }) => (_jsx(DataTable, { columns: columns, rows: rows, units: units })),
139
181
  rows: ({ items }, { depth }) => _jsx(Rows, { items: items, depth: depth }),
140
182
  fields: ({ row }, { depth, units }) => _jsx(Fields, { row: row, depth: depth, units: units }),
183
+ parts: ({ parts }) => _jsx(Parts, { parts: parts }),
141
184
  };
142
185
  function Node({ value: raw, depth, units, }) {
143
186
  const t = useLabels();
@@ -182,7 +225,9 @@ export function ShapedData({ value: raw, title, lead, ledKeys = [] }) {
182
225
  const left = Object.entries(value).filter(([key]) => !ledKeys.includes(key));
183
226
  return left.length ? Object.fromEntries(left) : undefined;
184
227
  }, [value, ledKeys]);
185
- if (text.length > LARGE_JSON_CHARS) {
228
+ // why: Base64 media draws as a picture or a player, not as text, so only the rest counts toward the limit.
229
+ const tooLarge = useMemo(() => text.length > LARGE_JSON_CHARS && layoutChars(value) > LARGE_JSON_CHARS, [text, value]);
230
+ if (tooLarge) {
186
231
  const size = new Intl.NumberFormat(locale, {
187
232
  style: 'unit',
188
233
  unit: 'kilobyte',
package/ui/labels.d.ts CHANGED
@@ -280,6 +280,16 @@ export declare const THEOREM_UI_CATALOG: {
280
280
  readonly description: "Rows past the shown ones, left to the JSON view.";
281
281
  readonly params: readonly ["count"];
282
282
  };
283
+ readonly '@theorem.data.image': {
284
+ readonly defaultMessage: "Image {index}";
285
+ readonly description: "An image in tool data, named by its place among the images and videos.";
286
+ readonly params: readonly ["index"];
287
+ };
288
+ readonly '@theorem.data.video': {
289
+ readonly defaultMessage: "Video {index}";
290
+ readonly description: "A video in tool data, named by its place among the images and videos.";
291
+ readonly params: readonly ["index"];
292
+ };
283
293
  readonly '@theorem.data.large': {
284
294
  readonly defaultMessage: "Too large to lay out — showing the first {size} of its JSON";
285
295
  readonly description: "A payload too big to draw by its shape, shown as the start of its JSON.";
package/ui/labels.js CHANGED
@@ -255,6 +255,16 @@ export const THEOREM_UI_CATALOG = {
255
255
  description: 'Rows past the shown ones, left to the JSON view.',
256
256
  params: ['count'],
257
257
  },
258
+ '@theorem.data.image': {
259
+ defaultMessage: 'Image {index}',
260
+ description: 'An image in tool data, named by its place among the images and videos.',
261
+ params: ['index'],
262
+ },
263
+ '@theorem.data.video': {
264
+ defaultMessage: 'Video {index}',
265
+ description: 'A video in tool data, named by its place among the images and videos.',
266
+ params: ['index'],
267
+ },
258
268
  '@theorem.data.large': {
259
269
  defaultMessage: 'Too large to lay out — showing the first {size} of its JSON',
260
270
  description: 'A payload too big to draw by its shape, shown as the start of its JSON.',