@pipelex/mthds-form 0.5.0 → 0.7.0

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.
Files changed (38) hide show
  1. package/dist/chunk-2JWRG53S.js +33 -0
  2. package/dist/chunk-2JWRG53S.js.map +1 -0
  3. package/dist/chunk-45C2MKK6.js +94 -0
  4. package/dist/chunk-45C2MKK6.js.map +1 -0
  5. package/dist/chunk-4BSBBG2T.js +80 -0
  6. package/dist/chunk-4BSBBG2T.js.map +1 -0
  7. package/dist/chunk-IIU3YK4O.js +16 -0
  8. package/dist/chunk-IIU3YK4O.js.map +1 -0
  9. package/dist/{chunk-IIJRJGZQ.js → chunk-J4L32LD6.js} +14 -5
  10. package/dist/chunk-J4L32LD6.js.map +1 -0
  11. package/dist/{chunk-IM2AHWBR.js → chunk-R7B7ID25.js} +3 -3
  12. package/dist/{chunk-IM2AHWBR.js.map → chunk-R7B7ID25.js.map} +1 -1
  13. package/dist/{chunk-5Q2W6HBA.js → chunk-T4NSAGOS.js} +3 -3
  14. package/dist/{chunk-5Q2W6HBA.js.map → chunk-T4NSAGOS.js.map} +1 -1
  15. package/dist/core/derive.d.ts +35 -2
  16. package/dist/core/derive.js +2 -1
  17. package/dist/core/file-formats.d.ts +102 -0
  18. package/dist/core/file-formats.js +3 -0
  19. package/dist/core/file-formats.js.map +1 -0
  20. package/dist/core/gate.js +2 -2
  21. package/dist/core/index.d.ts +6 -2
  22. package/dist/core/index.js +8 -4
  23. package/dist/core/native-content.d.ts +219 -0
  24. package/dist/core/native-content.js +4 -0
  25. package/dist/core/native-content.js.map +1 -0
  26. package/dist/core/output-form.d.ts +45 -0
  27. package/dist/core/output-form.js +5 -0
  28. package/dist/core/output-form.js.map +1 -0
  29. package/dist/core/stuff-files.d.ts +52 -0
  30. package/dist/core/stuff-files.js +5 -0
  31. package/dist/core/stuff-files.js.map +1 -0
  32. package/dist/core/values.js +3 -2
  33. package/dist/react/index.d.ts +278 -1
  34. package/dist/react/index.js +1358 -12
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/styles.css +1 -1
  37. package/package.json +17 -5
  38. package/dist/chunk-IIJRJGZQ.js.map +0 -1
@@ -1,19 +1,25 @@
1
1
  "use client";
2
+ import { collectStuffFiles } from '../chunk-4BSBBG2T.js';
3
+ import { isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isViewableUrl, readDateContent, formatDateContent, readImageContent, readDocumentContent } from '../chunk-45C2MKK6.js';
4
+ import { conceptCategory } from '../chunk-25I6JDIK.js';
5
+ import { isAcceptedFile, acceptMapForKind } from '../chunk-2JWRG53S.js';
2
6
  import { isFilled } from '../chunk-PM55AHRO.js';
3
7
  import { toStoredDateValue, toDateInputValue } from '../chunk-GKV77RAS.js';
4
8
  import { ownProp } from '../chunk-V3OV2XQT.js';
5
9
  import { clsx } from 'clsx';
6
10
  import { twMerge } from 'tailwind-merge';
7
- import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image, FileText, CalendarDays, Type, Loader2, Upload, Link2, ImageOff, EyeOff, Eye, X, Minus, Plus, Trash2 } from 'lucide-react';
11
+ import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image, FileText, CalendarDays, Type, Loader2, Upload, Link2, ImageOff, EyeOff, Eye, X, Minus, Plus, Trash2, Copy, Download, ChevronRight } from 'lucide-react';
8
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
13
  import * as React2 from 'react';
10
- import { createContext, use, useId, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
14
+ import { createContext, use, useId, useMemo, useContext, useState, useRef, useCallback, useEffect, Fragment as Fragment$1, useLayoutEffect } from 'react';
11
15
  import * as SwitchPrimitives from '@radix-ui/react-switch';
12
16
  import { useDropzone } from 'react-dropzone';
13
17
  import * as SelectPrimitive from '@radix-ui/react-select';
14
18
  import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
15
19
  import * as TogglePrimitive from '@radix-ui/react-toggle';
16
20
  import { cva } from 'class-variance-authority';
21
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
22
+ import { marked } from 'marked';
17
23
 
18
24
  function cn(...inputs) {
19
25
  return twMerge(clsx(inputs));
@@ -164,6 +170,23 @@ var DEFAULT_FIELD_STRINGS = {
164
170
  preview: "Preview",
165
171
  removeFileAria: "Remove file",
166
172
  previewUnavailablePdf: "Preview unavailable - open to view.",
173
+ // U+002D HYPHEN-MINUS, deliberately: not an en dash, not an em dash.
174
+ resultAbsent: "-",
175
+ resultAbsentDescription: "Not provided",
176
+ resultUndescribed: "No output descriptor for this pipe \u2014 showing the raw value.",
177
+ toggleRowDetails: (index) => `Show or hide the details of row ${index}`,
178
+ rowDetailsColumn: "Details",
179
+ copyUrl: "Copy the URL",
180
+ copyText: "Copy the text",
181
+ download: "Download",
182
+ downloading: "Downloading\u2026",
183
+ viewRendered: "Result",
184
+ viewJson: "JSON",
185
+ copyJson: "Copy the JSON",
186
+ resultViewGroup: "Result view",
187
+ yes: "Yes",
188
+ no: "No",
189
+ unsupportedFileType: (accept) => `That file type is not supported. Accepted formats: ${accept}.`,
167
190
  hideOptionalFields: "Hide optional fields",
168
191
  hideOptionalInputs: "Hide optional inputs",
169
192
  optionalFieldsCount: (count) => count === 1 ? "1 optional field" : `${count} optional fields`,
@@ -195,13 +218,13 @@ function FieldShell({
195
218
  const s = useFieldStrings();
196
219
  const presentation = useFieldPresentation();
197
220
  const isApp = presentation === "app";
198
- const Label2 = htmlFor ? "label" : "div";
221
+ const Label3 = htmlFor ? "label" : "div";
199
222
  const labelText = fieldLabel(title, name, presentation);
200
223
  const showLabel = labelText.trim() !== "";
201
224
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
202
225
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
203
226
  showLabel && /* @__PURE__ */ jsx(
204
- Label2,
227
+ Label3,
205
228
  {
206
229
  ...htmlFor ? { htmlFor } : {},
207
230
  className: cn(
@@ -212,7 +235,7 @@ function FieldShell({
212
235
  }
213
236
  ),
214
237
  !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef, category }),
215
- !required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
238
+ !required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: s.optionalBadge })
216
239
  ] }),
217
240
  description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: description }),
218
241
  isApp && error ? /* @__PURE__ */ jsx("div", { className: "rounded-lg ring-1 ring-destructive/70 ring-offset-4 ring-offset-background", children }) : children,
@@ -259,9 +282,7 @@ function DateField({ field, value, onChange, id, error, disabled }) {
259
282
  var IMAGE_EXT_RE = /\.(jpg|jpeg|png|gif|webp|bmp|svg|avif)(\?|$)/i;
260
283
  var PDF_EXT_RE = /\.pdf(\?|$)/i;
261
284
  var DATA_URL_MIME_RE = /^data:([^;,]+)/i;
262
- function isDirectlyViewable(url) {
263
- return !!url && (/^https?:/i.test(url) || /^data:/i.test(url) || /^blob:/i.test(url));
264
- }
285
+ var isDirectlyViewable = isViewableUrl;
265
286
  function dataUrlMime(url) {
266
287
  return DATA_URL_MIME_RE.exec(url)?.[1]?.toLowerCase();
267
288
  }
@@ -296,18 +317,30 @@ function FileField({
296
317
  setLocalPreview(next);
297
318
  }, []);
298
319
  useEffect(() => () => setLocal(null), [setLocal]);
320
+ const [rejected, setRejected] = useState(null);
299
321
  const handleFile = useCallback(
300
322
  (file) => {
323
+ if (!isAcceptedFile(field.kind, file)) {
324
+ setRejected(file.name);
325
+ return;
326
+ }
327
+ setRejected(null);
301
328
  setLocal({ objectUrl: URL.createObjectURL(file), type: file.type });
302
329
  onDropFile(file);
303
330
  },
304
- [setLocal, onDropFile]
331
+ [field.kind, setLocal, onDropFile]
305
332
  );
306
333
  const busy = disabled || uploading;
307
334
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
308
335
  maxFiles: 1,
309
336
  multiple: false,
310
337
  disabled: busy,
338
+ // Filters the OS picker so a wrong file is hard to choose in the first
339
+ // place. It is NOT the enforcement: a drag-and-drop bypasses the picker
340
+ // entirely, and a browser reporting an empty `File.type` slips through
341
+ // react-dropzone's own matcher. `handleFile` is where the answer is
342
+ // decided; this is the affordance in front of it.
343
+ accept: acceptMapForKind(field.kind),
311
344
  // The tab stop belongs on the INPUT, not on this div - see the root element
312
345
  // below. Without this, react-dropzone puts `tabIndex: 0` and its own key
313
346
  // handlers on a `role="presentation"` div, and the element a keyboard or
@@ -316,12 +349,21 @@ function FileField({
316
349
  onDrop: (files) => {
317
350
  const file = files[0];
318
351
  if (file) handleFile(file);
352
+ },
353
+ // react-dropzone rejects a file its own matcher refuses without ever calling
354
+ // `onDrop`, so without this a wrong drop is silently swallowed - the control
355
+ // simply does nothing, which reads as a broken dropzone rather than a
356
+ // refused file.
357
+ onDropRejected: (rejections) => {
358
+ const name = rejections[0]?.file.name;
359
+ if (name) setRejected(name);
319
360
  }
320
361
  });
321
362
  const clear = useCallback(() => {
322
363
  setLocal(null);
323
364
  setResolved(null);
324
365
  setPreviewOpen(false);
366
+ setRejected(null);
325
367
  onChange(void 0);
326
368
  }, [setLocal, onChange]);
327
369
  const localIsCurrent = !!localPreview && (localPreview.boundUrl === void 0 || localPreview.boundUrl === value?.url);
@@ -424,6 +466,12 @@ function FileField({
424
466
  }
425
467
  )
426
468
  ),
469
+ rejected && !busy && /* @__PURE__ */ jsxs("p", { role: "alert", className: "text-[12px] text-destructive", children: [
470
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: rejected }),
471
+ " \u2014",
472
+ " ",
473
+ s.unsupportedFileType(field.accept ?? "")
474
+ ] }),
427
475
  !showUrl ? /* @__PURE__ */ jsxs(
428
476
  "button",
429
477
  {
@@ -928,7 +976,7 @@ function ObjectField({ field, value, onChange, id, error, env }) {
928
976
  }
929
977
  ),
930
978
  !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "structured" }),
931
- !field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
979
+ !field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: s.optionalBadge })
932
980
  ] }),
933
981
  field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
934
982
  /* @__PURE__ */ jsxs(
@@ -997,7 +1045,7 @@ function ListField({ field, value, onChange, id, error, env }) {
997
1045
  ),
998
1046
  !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "list" }),
999
1047
  /* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: field.itemCount !== void 0 && field.maxItemCount === field.itemCount ? s.itemsCountOf(items.length, field.itemCount) : s.itemsCount(items.length) }),
1000
- !field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
1048
+ !field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground", children: s.optionalBadge })
1001
1049
  ] }),
1002
1050
  field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
1003
1051
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -1265,7 +1313,1305 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
1265
1313
  );
1266
1314
  }
1267
1315
  }
1316
+ var ResultEnvContext = createContext({});
1317
+ function ResultEnvProvider({
1318
+ resolveUrl,
1319
+ resolveShareUrl,
1320
+ children
1321
+ }) {
1322
+ const env = useMemo(() => ({ resolveUrl, resolveShareUrl }), [resolveUrl, resolveShareUrl]);
1323
+ return /* @__PURE__ */ jsx(ResultEnvContext, { value: env, children });
1324
+ }
1325
+ function useResolvedUrl(url) {
1326
+ return use(ResultEnvContext).resolveUrl?.(url) ?? url;
1327
+ }
1328
+ function useResolveUrl() {
1329
+ return use(ResultEnvContext).resolveUrl;
1330
+ }
1331
+ function useResolveShareUrl() {
1332
+ return use(ResultEnvContext).resolveShareUrl;
1333
+ }
1334
+ var TooltipProvider = TooltipPrimitive.Provider;
1335
+ var TooltipRoot = TooltipPrimitive.Root;
1336
+ var TooltipTrigger = TooltipPrimitive.Trigger;
1337
+ var TooltipContent = React2.forwardRef(({ className, sideOffset = 6, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(
1338
+ TooltipPrimitive.Content,
1339
+ {
1340
+ ref,
1341
+ sideOffset,
1342
+ className: cn(
1343
+ "z-50 max-w-xs rounded-md border border-border bg-popover px-2.5 py-1.5 text-[12px] leading-relaxed text-popover-foreground shadow-md",
1344
+ "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
1345
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
1346
+ className
1347
+ ),
1348
+ ...props
1349
+ }
1350
+ ) }));
1351
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
1352
+ var DEFAULT_IMG_SRC = "data: https:";
1353
+ function frameStyles(color, mutedColor, borderColor, font) {
1354
+ return `
1355
+ :root { color-scheme: inherit; }
1356
+ body {
1357
+ margin: 0;
1358
+ color: ${color};
1359
+ font-family: ${font};
1360
+ font-size: 13px;
1361
+ line-height: 1.55;
1362
+ background: transparent;
1363
+ overflow-wrap: anywhere;
1364
+ }
1365
+ :where(h1, h2, h3, h4, h5, h6) { margin: 0 0 0.4em; line-height: 1.25; }
1366
+ :where(h1) { font-size: 1.4em; }
1367
+ :where(h2) { font-size: 1.2em; }
1368
+ :where(h3) { font-size: 1.05em; }
1369
+ :where(p, ul, ol, table, pre, blockquote) { margin: 0 0 0.75em; }
1370
+ :where(ul, ol) { padding-inline-start: 1.25em; }
1371
+ :where(table) { border-collapse: collapse; width: 100%; }
1372
+ :where(th, td) {
1373
+ border: 1px solid ${borderColor};
1374
+ padding: 4px 8px;
1375
+ text-align: left;
1376
+ vertical-align: top;
1377
+ }
1378
+ :where(th) { font-weight: 600; }
1379
+ :where(caption) { caption-side: top; color: ${mutedColor}; padding-bottom: 4px; text-align: left; }
1380
+ :where(a) { color: inherit; }
1381
+ :where(code, pre) { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
1382
+ :where(pre) { white-space: pre-wrap; }
1383
+ :where(blockquote) { border-inline-start: 2px solid ${borderColor}; margin-inline-start: 0; padding-inline-start: 0.75em; color: ${mutedColor}; }
1384
+ :where(img) { max-width: 100%; height: auto; }
1385
+ :where(hr) { border: 0; border-top: 1px solid ${borderColor}; }
1386
+ :where(*:last-child) { margin-bottom: 0; }
1387
+ `;
1388
+ }
1389
+ function frameDocument(content, styles, imgSrc) {
1390
+ const body = content.cssClass ? (
1391
+ // The class the value states, honoured the way the runtime's own HTML
1392
+ // rendering honours it: as a wrapper, not as something merged into ours.
1393
+ `<div class="${escapeAttribute(content.cssClass)}">${content.innerHtml}</div>`
1394
+ ) : content.innerHtml;
1395
+ return [
1396
+ '<!doctype html><html><head><meta charset="utf-8">',
1397
+ `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src ${imgSrc}; base-uri 'none'; form-action 'none'">`,
1398
+ `<style>${styles}</style></head><body>${body}</body></html>`
1399
+ ].join("");
1400
+ }
1401
+ function escapeAttribute(value) {
1402
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
1403
+ }
1404
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
1405
+ function HtmlPreview({
1406
+ content,
1407
+ maxHeight = 1400,
1408
+ imgSrc = DEFAULT_IMG_SRC
1409
+ }) {
1410
+ const hostRef = useRef(null);
1411
+ const frameRef = useRef(null);
1412
+ const [doc, setDoc] = useState(null);
1413
+ const [height, setHeight] = useState(0);
1414
+ useIsomorphicLayoutEffect(() => {
1415
+ const host = hostRef.current;
1416
+ if (!host) return;
1417
+ const computed = getComputedStyle(host);
1418
+ setDoc(
1419
+ frameDocument(
1420
+ content,
1421
+ frameStyles(
1422
+ computed.color,
1423
+ computed.getPropertyValue("--muted-foreground").trim() || computed.color,
1424
+ computed.borderColor || computed.color,
1425
+ computed.fontFamily
1426
+ ),
1427
+ imgSrc
1428
+ )
1429
+ );
1430
+ }, [content, imgSrc]);
1431
+ const measure = () => {
1432
+ const body = frameRef.current?.contentDocument?.body;
1433
+ if (body) setHeight(body.scrollHeight);
1434
+ };
1435
+ useEffect(() => {
1436
+ if (!doc || !hostRef.current || typeof ResizeObserver === "undefined") return;
1437
+ const observer = new ResizeObserver(measure);
1438
+ observer.observe(hostRef.current);
1439
+ return () => observer.disconnect();
1440
+ }, [doc]);
1441
+ return /* @__PURE__ */ jsx(
1442
+ "div",
1443
+ {
1444
+ ref: hostRef,
1445
+ className: "overflow-hidden rounded-lg border border-border bg-card/40 px-3.5 py-3 text-[13px] text-foreground",
1446
+ children: doc === null ? null : /* @__PURE__ */ jsx(
1447
+ "iframe",
1448
+ {
1449
+ ref: frameRef,
1450
+ title: content.cssClass ? `HTML result (${content.cssClass})` : "HTML result",
1451
+ srcDoc: doc,
1452
+ onLoad: measure,
1453
+ sandbox: "allow-same-origin",
1454
+ style: {
1455
+ display: "block",
1456
+ width: "100%",
1457
+ border: 0,
1458
+ height: height === 0 ? void 0 : Math.min(height, maxHeight),
1459
+ maxHeight
1460
+ }
1461
+ }
1462
+ )
1463
+ }
1464
+ );
1465
+ }
1466
+ var LEX_OPTIONS = { gfm: true, breaks: true };
1467
+ function Markdown({ text, className }) {
1468
+ const tokens = React2.useMemo(() => marked.lexer(text, LEX_OPTIONS), [text]);
1469
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-2 text-[13px] leading-relaxed text-foreground", className), children: /* @__PURE__ */ jsx(Blocks, { tokens }) });
1470
+ }
1471
+ var HEADING_CLASS = {
1472
+ 1: "text-[16px] font-semibold",
1473
+ 2: "text-[14.5px] font-semibold",
1474
+ 3: "text-[13.5px] font-semibold",
1475
+ 4: "text-[13px] font-semibold",
1476
+ 5: "text-[13px] font-semibold",
1477
+ 6: "text-[13px] font-semibold"
1478
+ };
1479
+ function Blocks({ tokens }) {
1480
+ return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((token, index) => /* @__PURE__ */ jsx(Block, { token }, index)) });
1481
+ }
1482
+ function Block({ token }) {
1483
+ switch (token.type) {
1484
+ case "space":
1485
+ return null;
1486
+ case "heading": {
1487
+ const heading = token;
1488
+ const level = Math.min(Math.max(heading.depth, 1), 6);
1489
+ const Tag = `h${level}`;
1490
+ return /* @__PURE__ */ jsx(Tag, { className: cn("mt-3 first:mt-0", HEADING_CLASS[level]), children: /* @__PURE__ */ jsx(Inline, { tokens: heading.tokens }) });
1491
+ }
1492
+ case "paragraph":
1493
+ return /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(Inline, { tokens: token.tokens }) });
1494
+ // A `text` token in BLOCK position, which is what a tight list's items are
1495
+ // made of: the lexer wraps a loose item's content in a paragraph and a
1496
+ // tight one's in a bare `text`. Without this arm it fell through to the raw
1497
+ // fallback below and a bulleted `**Type:** …` rendered with its asterisks
1498
+ // showing - typeset everywhere except inside a list, which is where model
1499
+ // output puts most of its emphasis.
1500
+ case "text":
1501
+ return /* @__PURE__ */ jsx(Inline, { tokens: token.tokens ?? [token] });
1502
+ case "list": {
1503
+ const list = token;
1504
+ const Tag = list.ordered ? "ol" : "ul";
1505
+ return /* @__PURE__ */ jsx(
1506
+ Tag,
1507
+ {
1508
+ className: cn("space-y-1 ps-5", list.ordered ? "list-decimal" : "list-disc"),
1509
+ ...list.ordered && list.start !== 1 ? { start: Number(list.start) } : {},
1510
+ children: list.items.map((item, index) => /* @__PURE__ */ jsxs("li", { className: "marker:text-muted-foreground", children: [
1511
+ item.task ? /* @__PURE__ */ jsx(
1512
+ "input",
1513
+ {
1514
+ type: "checkbox",
1515
+ checked: item.checked ?? false,
1516
+ disabled: true,
1517
+ readOnly: true,
1518
+ className: "me-1.5 align-middle"
1519
+ }
1520
+ ) : null,
1521
+ /* @__PURE__ */ jsx(Blocks, { tokens: item.tokens })
1522
+ ] }, index))
1523
+ }
1524
+ );
1525
+ }
1526
+ case "code": {
1527
+ const code = token;
1528
+ return (
1529
+ // WRAPPED, not scrolled. A code block in a side panel is usually not
1530
+ // code at all: it is a model that opened a fence and did not close it,
1531
+ // or closed one it never opened, and the rest of a report ends up
1532
+ // inside it. Scrolling that sideways hides the text behind a scrollbar
1533
+ // for no gain — the lines are prose, and prose wraps. Real code loses
1534
+ // its column alignment and keeps every character, which is the better
1535
+ // half of that trade at this width.
1536
+ /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap break-words rounded-md border border-border bg-card/40 p-2.5 text-[12px]", children: /* @__PURE__ */ jsx("code", { children: code.text }) })
1537
+ );
1538
+ }
1539
+ case "blockquote":
1540
+ return /* @__PURE__ */ jsx("blockquote", { className: "border-s-2 border-border ps-3 text-muted-foreground", children: /* @__PURE__ */ jsx(Blocks, { tokens: token.tokens }) });
1541
+ case "hr":
1542
+ return /* @__PURE__ */ jsx("hr", { className: "border-border" });
1543
+ case "table": {
1544
+ const table = token;
1545
+ return (
1546
+ // The same overflow rule the result tables follow: a wide table scrolls
1547
+ // inside its own box rather than widening the panel around it.
1548
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border border-border", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full border-collapse text-[12.5px]", children: [
1549
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "border-b border-border bg-card/40", children: table.header.map((cell, index) => /* @__PURE__ */ jsx(
1550
+ "th",
1551
+ {
1552
+ scope: "col",
1553
+ className: "px-2.5 py-1.5 text-start font-semibold",
1554
+ style: alignOf(table.align[index]),
1555
+ children: /* @__PURE__ */ jsx(Inline, { tokens: cell.tokens })
1556
+ },
1557
+ index
1558
+ )) }) }),
1559
+ /* @__PURE__ */ jsx("tbody", { children: table.rows.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { className: "border-b border-border/60 last:border-b-0", children: row.map((cell, cellIndex) => /* @__PURE__ */ jsx(
1560
+ "td",
1561
+ {
1562
+ className: "px-2.5 py-1.5 align-top",
1563
+ style: alignOf(table.align[cellIndex]),
1564
+ children: /* @__PURE__ */ jsx(Inline, { tokens: cell.tokens })
1565
+ },
1566
+ cellIndex
1567
+ )) }, rowIndex)) })
1568
+ ] }) })
1569
+ );
1570
+ }
1571
+ // `html` and anything this version of the lexer grows that we have no arm
1572
+ // for: shown as its own source text. Rendering markup we have decided not to
1573
+ // execute as the characters it is made of is the honest fallback - it never
1574
+ // silently drops content, and it never injects any.
1575
+ default:
1576
+ return /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: rawOf(token) });
1577
+ }
1578
+ }
1579
+ function Inline({ tokens }) {
1580
+ if (!tokens) return null;
1581
+ return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((token, index) => /* @__PURE__ */ jsx(InlineToken, { token }, index)) });
1582
+ }
1583
+ function InlineToken({ token }) {
1584
+ switch (token.type) {
1585
+ case "text": {
1586
+ const text = token;
1587
+ return text.tokens ? /* @__PURE__ */ jsx(Inline, { tokens: text.tokens }) : /* @__PURE__ */ jsx(Fragment, { children: decode(text.text) });
1588
+ }
1589
+ case "escape":
1590
+ return /* @__PURE__ */ jsx(Fragment, { children: token.text });
1591
+ case "strong":
1592
+ return /* @__PURE__ */ jsx("strong", { className: "font-semibold", children: /* @__PURE__ */ jsx(Inline, { tokens: token.tokens }) });
1593
+ case "em":
1594
+ return /* @__PURE__ */ jsx("em", { className: "italic", children: /* @__PURE__ */ jsx(Inline, { tokens: token.tokens }) });
1595
+ case "del":
1596
+ return /* @__PURE__ */ jsx("del", { className: "line-through", children: /* @__PURE__ */ jsx(Inline, { tokens: token.tokens }) });
1597
+ case "codespan":
1598
+ return /* @__PURE__ */ jsx("code", { className: "rounded border border-border bg-card/40 px-1 py-0.5 text-[0.92em]", children: decode(token.text) });
1599
+ case "br":
1600
+ return /* @__PURE__ */ jsx("br", {});
1601
+ case "link": {
1602
+ const link = token;
1603
+ const href = safeHref(link.href);
1604
+ if (!href) return /* @__PURE__ */ jsx(Inline, { tokens: link.tokens });
1605
+ return /* @__PURE__ */ jsx(
1606
+ "a",
1607
+ {
1608
+ href,
1609
+ target: "_blank",
1610
+ rel: "noreferrer noopener",
1611
+ className: "underline underline-offset-2",
1612
+ children: /* @__PURE__ */ jsx(Inline, { tokens: link.tokens })
1613
+ }
1614
+ );
1615
+ }
1616
+ case "image": {
1617
+ const image = token;
1618
+ const src = safeHref(image.href);
1619
+ if (!src) return /* @__PURE__ */ jsx(Fragment, { children: image.text });
1620
+ return /* @__PURE__ */ jsx("img", { src, alt: image.text, className: "max-w-full rounded-md" });
1621
+ }
1622
+ default:
1623
+ return /* @__PURE__ */ jsx(Fragment, { children: rawOf(token) });
1624
+ }
1625
+ }
1626
+ function safeHref(href) {
1627
+ const trimmed = href.trim();
1628
+ if (/^(https?:|mailto:)/i.test(trimmed)) return trimmed;
1629
+ if (/^[#/]/.test(trimmed)) return trimmed;
1630
+ return void 0;
1631
+ }
1632
+ function alignOf(align) {
1633
+ return align ? { textAlign: align } : void 0;
1634
+ }
1635
+ function rawOf(token) {
1636
+ return "raw" in token && typeof token.raw === "string" ? token.raw : "";
1637
+ }
1638
+ var ENTITIES = {
1639
+ "&amp;": "&",
1640
+ "&lt;": "<",
1641
+ "&gt;": ">",
1642
+ "&quot;": '"',
1643
+ "&#39;": "'",
1644
+ "&#x27;": "'"
1645
+ };
1646
+ function decode(text) {
1647
+ return text.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g, (entity) => ENTITIES[entity] ?? entity);
1648
+ }
1649
+ function unwrap(field, value) {
1650
+ if (!field.contentKey) return value;
1651
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
1652
+ const unwrapped = ownProp(value, field.contentKey);
1653
+ return unwrapped === void 0 ? value : unwrapped;
1654
+ }
1655
+ function Described({ description, children }) {
1656
+ if (!description) return /* @__PURE__ */ jsx(Fragment, { children });
1657
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
1658
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1659
+ "span",
1660
+ {
1661
+ tabIndex: 0,
1662
+ className: "-mx-1 cursor-help rounded px-1 outline-none transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring",
1663
+ children
1664
+ }
1665
+ ) }),
1666
+ /* @__PURE__ */ jsx(TooltipContent, { children: description })
1667
+ ] }) });
1668
+ }
1669
+ function ResultHeader({ field }) {
1670
+ return /* @__PURE__ */ jsx(Label2, { field, describe: true });
1671
+ }
1672
+ function Label2({ field, describe = false }) {
1673
+ const presentation = useFieldPresentation();
1674
+ const isApp = presentation === "app";
1675
+ const labelText = fieldLabel(field.title, field.name, presentation);
1676
+ if (labelText.trim() === "") {
1677
+ return /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: conceptCategory(field) });
1678
+ }
1679
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
1680
+ /* @__PURE__ */ jsx(Described, { description: describe ? field.description : void 0, children: /* @__PURE__ */ jsx(
1681
+ "span",
1682
+ {
1683
+ className: cn(
1684
+ "text-[13px] font-semibold text-foreground",
1685
+ isApp ? "text-[13.5px]" : "font-mono"
1686
+ ),
1687
+ children: labelText
1688
+ }
1689
+ ) }),
1690
+ !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: conceptCategory(field) })
1691
+ ] });
1692
+ }
1693
+ function Absent() {
1694
+ const s = useFieldStrings();
1695
+ return /* @__PURE__ */ jsxs("span", { className: "text-[13px] text-muted-foreground", children: [
1696
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: s.resultAbsent }),
1697
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: s.resultAbsentDescription })
1698
+ ] });
1699
+ }
1700
+ function stringifyValue(value) {
1701
+ try {
1702
+ const text = JSON.stringify(
1703
+ value,
1704
+ (_key, member) => typeof member === "bigint" ? member.toString() : member
1705
+ );
1706
+ if (text === void 0 || text === "{}" || text === "[]") return void 0;
1707
+ return JSON.stringify(JSON.parse(text), null, 2);
1708
+ } catch {
1709
+ const keys = Array.isArray(value) ? [] : Object.keys(value);
1710
+ return keys.length > 0 ? `{ ${keys.join(", ")} }` : void 0;
1711
+ }
1712
+ }
1713
+ function RawValue({ value, compact }) {
1714
+ const text = stringifyValue(value);
1715
+ if (text === void 0) return /* @__PURE__ */ jsx(Absent, {});
1716
+ return compact ? /* @__PURE__ */ jsx("span", { title: text, className: "truncate font-mono text-[12px] text-foreground", children: text }) : /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-words rounded-md border border-border bg-card/40 px-2.5 py-1.5 font-mono text-[12px] leading-relaxed text-foreground", children: text });
1717
+ }
1718
+ function Scalar({ value, compact = false }) {
1719
+ if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx(Absent, {});
1720
+ if (typeof value === "object") return /* @__PURE__ */ jsx(RawValue, { value, compact });
1721
+ return /* @__PURE__ */ jsx(
1722
+ "span",
1723
+ {
1724
+ className: cn(
1725
+ "text-[13px] leading-relaxed text-foreground",
1726
+ compact ? "whitespace-nowrap" : "whitespace-pre-wrap"
1727
+ ),
1728
+ children: String(value)
1729
+ }
1730
+ );
1731
+ }
1732
+ function Bool({ value, compact = false }) {
1733
+ const s = useFieldStrings();
1734
+ if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx(Absent, {});
1735
+ if (typeof value !== "boolean") return /* @__PURE__ */ jsx(Scalar, { value, compact });
1736
+ return /* @__PURE__ */ jsx("span", { className: "text-[13px] text-foreground", children: value ? s.yes : s.no });
1737
+ }
1738
+ function DateValue({ value }) {
1739
+ const content = readDateContent(value);
1740
+ if (!content) return /* @__PURE__ */ jsx(Absent, {});
1741
+ return /* @__PURE__ */ jsx("span", { className: "text-[13px] text-foreground", children: formatDateContent(content) });
1742
+ }
1743
+ function fileLabel(url, filename) {
1744
+ if (filename) return filename;
1745
+ const path = url.split(/[?#]/)[0] ?? url;
1746
+ const segment = path.split("/").filter(Boolean).pop();
1747
+ return segment && segment.length > 0 ? segment : url;
1748
+ }
1749
+ function CopyButton({
1750
+ value,
1751
+ provide,
1752
+ label,
1753
+ title
1754
+ }) {
1755
+ const [copied, setCopied] = useState(false);
1756
+ if (typeof navigator === "undefined" || !navigator.clipboard) return null;
1757
+ return /* @__PURE__ */ jsx(
1758
+ "button",
1759
+ {
1760
+ type: "button",
1761
+ onClick: () => {
1762
+ const done = () => {
1763
+ setCopied(true);
1764
+ window.setTimeout(() => setCopied(false), 1500);
1765
+ };
1766
+ if (!provide) {
1767
+ void navigator.clipboard.writeText(value ?? "").then(done);
1768
+ return;
1769
+ }
1770
+ const text = provide();
1771
+ if (typeof ClipboardItem === "function" && "write" in navigator.clipboard) {
1772
+ void navigator.clipboard.write([
1773
+ new ClipboardItem({
1774
+ "text/plain": text.then((t) => new Blob([t], { type: "text/plain" }))
1775
+ })
1776
+ ]).then(done, () => void text.then((t) => navigator.clipboard.writeText(t)).then(done));
1777
+ return;
1778
+ }
1779
+ void text.then((t) => navigator.clipboard.writeText(t)).then(done);
1780
+ },
1781
+ ...title ? { title } : {},
1782
+ "aria-label": label,
1783
+ className: "shrink-0 rounded p-0.5 text-muted-foreground hover:bg-card hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1",
1784
+ children: copied ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Copy, { "aria-hidden": true, className: "size-3.5" })
1785
+ }
1786
+ );
1787
+ }
1788
+ function CopyUrlButton({ url, storageUrl }) {
1789
+ const s = useFieldStrings();
1790
+ const resolveShare = useResolveShareUrl();
1791
+ const provide = async () => {
1792
+ if (resolveShare && storageUrl) {
1793
+ const shared = await resolveShare(storageUrl);
1794
+ if (shared) return shared;
1795
+ }
1796
+ return absoluteUrl(url);
1797
+ };
1798
+ return /* @__PURE__ */ jsx(CopyButton, { provide, label: s.copyUrl, title: url });
1799
+ }
1800
+ function absoluteUrl(url) {
1801
+ if (!/^\/(?!\/)/.test(url) || typeof window === "undefined") return url;
1802
+ return `${window.location.origin}${url}`;
1803
+ }
1804
+ function FileRef({
1805
+ url,
1806
+ storageUrl,
1807
+ mimeType,
1808
+ filename
1809
+ }) {
1810
+ const label = fileLabel(url, filename);
1811
+ const title = mimeType ? `${url} \xB7 ${mimeType}` : url;
1812
+ return /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1813
+ isViewableUrl(url) ? /* @__PURE__ */ jsx(
1814
+ "a",
1815
+ {
1816
+ href: url,
1817
+ target: "_blank",
1818
+ rel: "noreferrer",
1819
+ title,
1820
+ className: "min-w-0 truncate font-mono text-[12px] text-foreground underline underline-offset-2",
1821
+ children: label
1822
+ }
1823
+ ) : /* @__PURE__ */ jsx(
1824
+ "span",
1825
+ {
1826
+ title,
1827
+ className: "min-w-0 truncate font-mono text-[12px] text-muted-foreground",
1828
+ children: label
1829
+ }
1830
+ ),
1831
+ /* @__PURE__ */ jsx(CopyUrlButton, { url, ...storageUrl ? { storageUrl } : {} })
1832
+ ] });
1833
+ }
1834
+ function paintableUrl(content, resolve) {
1835
+ const resolved = resolve?.(content.url);
1836
+ if (isViewableUrl(resolved)) return resolved;
1837
+ if (isViewableUrl(content.publicUrl)) return content.publicUrl;
1838
+ return isViewableUrl(content.url) ? content.url : void 0;
1839
+ }
1840
+ var PREVIEWABLE_EXT_RE = /\.(pdf|png|jpe?g|gif|webp|avif|svg)(\?|#|$)/i;
1841
+ var PREVIEWABLE_MIME_RE = /^(application\/pdf|image\/)/i;
1842
+ function previewableUrl(content, resolve) {
1843
+ const url = paintableUrl(content, resolve);
1844
+ if (!url) return void 0;
1845
+ const named = content.filename ?? url;
1846
+ const renderable = content.mimeType ? PREVIEWABLE_MIME_RE.test(content.mimeType) : PREVIEWABLE_EXT_RE.test(named);
1847
+ return renderable ? url : void 0;
1848
+ }
1849
+ function DocumentPreview({ url, name }) {
1850
+ return /* @__PURE__ */ jsx(
1851
+ "iframe",
1852
+ {
1853
+ src: url,
1854
+ title: name,
1855
+ loading: "lazy",
1856
+ referrerPolicy: "no-referrer",
1857
+ className: "h-[28rem] w-full rounded-lg border border-border bg-card/40"
1858
+ }
1859
+ );
1860
+ }
1861
+ function DocumentValue({ value }) {
1862
+ const s = useFieldStrings();
1863
+ const resolve = useResolveUrl();
1864
+ const [open, setOpen] = useState(false);
1865
+ const content = readDocumentContent(value);
1866
+ if (!content) return /* @__PURE__ */ jsx(Absent, {});
1867
+ const name = content.title ?? content.filename ?? fileLabel(content.url, content.filename);
1868
+ const preview = previewableUrl(content, resolve);
1869
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1870
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [
1871
+ /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
1872
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [
1873
+ name && /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] text-foreground", children: name }),
1874
+ /* @__PURE__ */ jsx(
1875
+ FileRef,
1876
+ {
1877
+ url: paintableUrl(content, resolve) ?? content.url,
1878
+ storageUrl: content.url,
1879
+ mimeType: content.mimeType,
1880
+ ...content.filename ? { filename: content.filename } : {}
1881
+ }
1882
+ ),
1883
+ content.snippet && /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-[12px] leading-relaxed text-muted-foreground", children: content.snippet })
1884
+ ] }),
1885
+ preview && /* @__PURE__ */ jsxs(
1886
+ "button",
1887
+ {
1888
+ type: "button",
1889
+ onClick: () => setOpen((current) => !current),
1890
+ "aria-expanded": open,
1891
+ className: "flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2 py-1 text-[12px] text-muted-foreground hover:bg-card hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1",
1892
+ children: [
1893
+ open ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": true, className: "size-3.5" }),
1894
+ s.preview
1895
+ ]
1896
+ }
1897
+ )
1898
+ ] }),
1899
+ preview && open && /* @__PURE__ */ jsx(DocumentPreview, { url: preview, name })
1900
+ ] });
1901
+ }
1902
+ function ImageValue({
1903
+ value,
1904
+ inGallery = false,
1905
+ inRow = false,
1906
+ compact = false
1907
+ }) {
1908
+ const s = useFieldStrings();
1909
+ const resolve = useResolveUrl();
1910
+ const content = readImageContent(value);
1911
+ if (!content) return /* @__PURE__ */ jsx(Absent, {});
1912
+ const src = paintableUrl(content, resolve);
1913
+ return /* @__PURE__ */ jsxs("div", { className: inGallery ? "space-y-1" : "space-y-1.5", children: [
1914
+ src ? (
1915
+ // Wrapped in a link: the picture is a PREVIEW of a file, and clicking a
1916
+ // preview to see the thing it previews is what a reader expects. The
1917
+ // reference underneath is the other half - a picture with no URL beside
1918
+ // it is a result you can look at and cannot use.
1919
+ /* @__PURE__ */ jsx(
1920
+ "a",
1921
+ {
1922
+ href: src,
1923
+ target: "_blank",
1924
+ rel: "noreferrer",
1925
+ title: content.url,
1926
+ className: inGallery || compact ? "inline-block" : "block w-full",
1927
+ children: /* @__PURE__ */ jsx(
1928
+ LoadingImage,
1929
+ {
1930
+ src,
1931
+ alt: content.caption ?? s.preview,
1932
+ className: cn(
1933
+ inGallery && "block aspect-square w-full object-cover",
1934
+ // A cell is one line tall. An image column rendered at the standalone
1935
+ // height turns every row into a picture and the table into a
1936
+ // slideshow, so a cell gets a thumbnail and the row's expansion gets
1937
+ // the picture.
1938
+ compact && "h-10 w-auto rounded border border-border object-cover",
1939
+ // On its own: the full width it is given, capped generously in
1940
+ // height. `max-h-64` was a 256px thumbnail in a panel several
1941
+ // times that tall — a generated report image is the RESULT here,
1942
+ // not a decoration beside one, and it was unreadable at that size.
1943
+ // `object-contain` keeps the aspect ratio when the cap bites.
1944
+ !inGallery && !compact && "max-h-[44rem] w-full rounded-lg border border-border object-contain"
1945
+ )
1946
+ }
1947
+ )
1948
+ }
1949
+ )
1950
+ ) : inRow ? (
1951
+ // The row variant of "nothing to paint": an icon and a name, exactly as
1952
+ // a document row reads, because that is what this has become.
1953
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [
1954
+ /* @__PURE__ */ jsx(ImageOff, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
1955
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
1956
+ FileRef,
1957
+ {
1958
+ url: content.url,
1959
+ mimeType: content.mimeType,
1960
+ ...content.filename ? { filename: content.filename } : {}
1961
+ }
1962
+ ) })
1963
+ ] })
1964
+ ) : inGallery ? (
1965
+ // Nothing to paint, so the tile says what it is rather than dumping a
1966
+ // ninety-character storage reference into a 140px box. This is what a
1967
+ // host with no resolver sees, so it has to be a design and not a
1968
+ // fallback.
1969
+ /* @__PURE__ */ jsxs("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-1.5 px-2 text-center", children: [
1970
+ /* @__PURE__ */ jsx(ImageOff, { "aria-hidden": true, className: "size-5 text-muted-foreground" }),
1971
+ /* @__PURE__ */ jsx(
1972
+ "span",
1973
+ {
1974
+ title: content.url,
1975
+ className: "w-full truncate font-mono text-[10.5px] text-muted-foreground",
1976
+ children: fileLabel(content.url, content.filename)
1977
+ }
1978
+ )
1979
+ ] })
1980
+ ) : /* @__PURE__ */ jsx(
1981
+ FileRef,
1982
+ {
1983
+ url: content.url,
1984
+ mimeType: content.mimeType,
1985
+ ...content.filename ? { filename: content.filename } : {}
1986
+ }
1987
+ ),
1988
+ src && !compact && /* @__PURE__ */ jsx("div", { className: inGallery ? "px-2.5" : void 0, children: /* @__PURE__ */ jsx(
1989
+ FileRef,
1990
+ {
1991
+ url: paintableUrl(content, resolve) ?? content.url,
1992
+ storageUrl: content.url,
1993
+ mimeType: content.mimeType,
1994
+ ...content.filename ? { filename: content.filename } : {}
1995
+ }
1996
+ ) }),
1997
+ content.caption && /* @__PURE__ */ jsx(
1998
+ "p",
1999
+ {
2000
+ className: cn(
2001
+ "text-[12px] leading-relaxed text-muted-foreground",
2002
+ inGallery && "px-2.5 pb-1.5"
2003
+ ),
2004
+ children: content.caption
2005
+ }
2006
+ )
2007
+ ] });
2008
+ }
2009
+ var TABULAR_KINDS = /* @__PURE__ */ new Set(["text", "number", "boolean", "date", "enum"]);
2010
+ function LeafValue({
2011
+ field,
2012
+ value: raw,
2013
+ compact = false
2014
+ }) {
2015
+ const value = unwrap(field, raw);
2016
+ switch (field.kind) {
2017
+ case "list": {
2018
+ const entries = Array.isArray(value) ? value : [];
2019
+ return TABULAR_KINDS.has(field.item.kind) ? /* @__PURE__ */ jsx(ScalarChips, { field: field.item, items: entries }) : /* @__PURE__ */ jsx(ItemCount, { count: entries.length });
2020
+ }
2021
+ case "prose":
2022
+ return compact ? /* @__PURE__ */ jsx(Scalar, { value, compact: true }) : typeof value === "string" && value !== "" ? /* @__PURE__ */ jsx(Markdown, { text: value }) : /* @__PURE__ */ jsx(Scalar, { value });
2023
+ case "boolean":
2024
+ return /* @__PURE__ */ jsx(Bool, { value, compact });
2025
+ case "date":
2026
+ return /* @__PURE__ */ jsx(DateValue, { value });
2027
+ case "document":
2028
+ return /* @__PURE__ */ jsx(DocumentValue, { value });
2029
+ case "image":
2030
+ return /* @__PURE__ */ jsx(ImageValue, { value, compact });
2031
+ default:
2032
+ return /* @__PURE__ */ jsx(Scalar, { value, compact });
2033
+ }
2034
+ }
2035
+ function LoadingImage({ src, alt, className }) {
2036
+ const [state, setState] = useState("loading");
2037
+ const [current, setCurrent] = useState(src);
2038
+ if (current !== src) {
2039
+ setCurrent(src);
2040
+ setState("loading");
2041
+ }
2042
+ return /* @__PURE__ */ jsxs("span", { className: "relative block", children: [
2043
+ state === "loading" && /* @__PURE__ */ jsx(
2044
+ "span",
2045
+ {
2046
+ role: "status",
2047
+ "aria-label": alt,
2048
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border border-border bg-card/40",
2049
+ children: /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-4 animate-spin text-muted-foreground" })
2050
+ }
2051
+ ),
2052
+ /* @__PURE__ */ jsx(
2053
+ "img",
2054
+ {
2055
+ src,
2056
+ alt,
2057
+ onLoad: () => setState("done"),
2058
+ onError: () => setState("done"),
2059
+ className: cn(className, state === "loading" && "min-h-40 opacity-0")
2060
+ }
2061
+ )
2062
+ ] });
2063
+ }
2064
+ function ScalarChips({ field, items }) {
2065
+ return (
2066
+ // `data-chips` is the hook the definition grid's value cell reaches through:
2067
+ // `text-right` has no effect on flex children, so a row of tags would keep
2068
+ // starting under its label while every value beside it ended at the right
2069
+ // edge. The attribute keeps that one selector out of this component's own
2070
+ // class list - chips are laid out the same way everywhere, and only the
2071
+ // caller knows which edge they should end on.
2072
+ /* @__PURE__ */ jsx("div", { "data-chips": true, className: "flex flex-wrap gap-1.5", children: items.map((item, index) => /* @__PURE__ */ jsx(
2073
+ "span",
2074
+ {
2075
+ className: "rounded-md border border-border bg-card/40 px-2 py-0.5 text-[12.5px] text-foreground",
2076
+ children: /* @__PURE__ */ jsx(LeafValue, { field, value: item })
2077
+ },
2078
+ index
2079
+ )) })
2080
+ );
2081
+ }
2082
+ function ItemCount({ count }) {
2083
+ const s = useFieldStrings();
2084
+ return /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-[12.5px] text-muted-foreground", children: s.itemsCount(count) });
2085
+ }
2086
+ function isInlineColumn(field) {
2087
+ if (field.kind === "list") return TABULAR_KINDS.has(field.item.kind);
2088
+ return TABULAR_KINDS.has(field.kind);
2089
+ }
2090
+ var CELL_LENGTH_LIMIT = 80;
2091
+ var EXPAND_COLUMN = "2rem";
2092
+ function fitsACellWhole(field) {
2093
+ switch (field.kind) {
2094
+ case "boolean":
2095
+ case "date":
2096
+ case "number":
2097
+ case "enum":
2098
+ return true;
2099
+ case "text":
2100
+ return field.maxLength !== void 0 && field.maxLength <= CELL_LENGTH_LIMIT;
2101
+ case "list":
2102
+ return fitsACellWhole(field.item);
2103
+ default:
2104
+ return false;
2105
+ }
2106
+ }
2107
+ function ObjectTable({
2108
+ columns,
2109
+ element,
2110
+ items,
2111
+ label
2112
+ }) {
2113
+ const s = useFieldStrings();
2114
+ const presentation = useFieldPresentation();
2115
+ const scrollerRef = useRef(null);
2116
+ const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
2117
+ const [viewportWidth, setViewportWidth] = useState();
2118
+ const canExpand = columns.some((column) => !fitsACellWhole(column));
2119
+ useEffect(() => {
2120
+ const scroller = scrollerRef.current;
2121
+ if (!scroller) return;
2122
+ const measure = () => setViewportWidth(scroller.clientWidth);
2123
+ measure();
2124
+ if (typeof ResizeObserver === "undefined") return;
2125
+ const observer = new ResizeObserver(measure);
2126
+ observer.observe(scroller);
2127
+ return () => observer.disconnect();
2128
+ }, []);
2129
+ const toggle = (index) => setExpanded((current) => {
2130
+ const next = new Set(current);
2131
+ if (!next.delete(index)) next.add(index);
2132
+ return next;
2133
+ });
2134
+ return (
2135
+ // `min-w-full` rather than `w-full`, and a floor under each cell: with
2136
+ // `w-full` the table is pinned to the container's width, so twelve columns
2137
+ // do not overflow - they crush, and a reading's date wraps onto four lines.
2138
+ // A floor makes the table wider than the panel instead, which is what the
2139
+ // scroller is for.
2140
+ //
2141
+ // A scrollable region must be reachable by keyboard. A table of values has
2142
+ // nothing to tab to, so the region itself takes focus; without `tabIndex` the
2143
+ // columns past the fold are reachable with a mouse and by no other means,
2144
+ // which the a11y gate catches as `scrollable-region-focusable`. The name
2145
+ // makes the focus stop announce itself rather than being a silent one.
2146
+ //
2147
+ // `group` rather than `region`, and that is not a detail: `region` is a
2148
+ // LANDMARK, so every table would enter the document's landmark list, and two
2149
+ // of them with the same name - which is what a result rendered twice, or a
2150
+ // structure holding two lists, produces - is `landmark-unique`. A group is
2151
+ // named without claiming to be a section of the page.
2152
+ /* @__PURE__ */ jsx(
2153
+ "div",
2154
+ {
2155
+ ref: scrollerRef,
2156
+ role: "group",
2157
+ "aria-label": label,
2158
+ tabIndex: 0,
2159
+ className: "overflow-x-auto rounded-lg border border-border",
2160
+ children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-full border-collapse text-[13px]", children: [
2161
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border bg-muted text-left", children: [
2162
+ canExpand && /* @__PURE__ */ jsx("th", { scope: "col", style: { width: EXPAND_COLUMN }, className: "px-1", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: s.rowDetailsColumn }) }),
2163
+ columns.map((column) => /* @__PURE__ */ jsx(
2164
+ "th",
2165
+ {
2166
+ scope: "col",
2167
+ className: cn(
2168
+ "whitespace-nowrap px-3 py-2 text-[12px] font-semibold text-foreground",
2169
+ presentation === "app" ? void 0 : "font-mono"
2170
+ ),
2171
+ children: /* @__PURE__ */ jsx(Described, { description: column.description, children: fieldLabel(column.title, column.name, presentation) })
2172
+ },
2173
+ column.name
2174
+ ))
2175
+ ] }) }),
2176
+ /* @__PURE__ */ jsx("tbody", { children: items.map((item, index) => {
2177
+ const isOpen = expanded.has(index);
2178
+ return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border/60 last:border-b-0", children: [
2179
+ canExpand && /* @__PURE__ */ jsx("td", { className: "px-1 align-top", children: /* @__PURE__ */ jsx(
2180
+ "button",
2181
+ {
2182
+ type: "button",
2183
+ onClick: () => toggle(index),
2184
+ "aria-expanded": isOpen,
2185
+ "aria-label": s.toggleRowDetails(index + 1),
2186
+ className: "mt-0.5 flex size-6 items-center justify-center rounded text-muted-foreground hover:bg-card hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1",
2187
+ children: isOpen ? /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true, className: "size-3.5" })
2188
+ }
2189
+ ) }),
2190
+ isOpen ? /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: "bg-card/40 p-0", children: /* @__PURE__ */ jsx(
2191
+ "div",
2192
+ {
2193
+ className: "sticky left-0 px-3.5 py-3",
2194
+ ...viewportWidth ? {
2195
+ style: {
2196
+ // Minus the chevron column: this cell starts
2197
+ // after it, so pinning it to the FULL visible
2198
+ // width pushes its right edge that far past the
2199
+ // scroller. See `EXPAND_COLUMN`.
2200
+ width: canExpand ? `calc(${viewportWidth}px - ${EXPAND_COLUMN})` : viewportWidth
2201
+ }
2202
+ } : {},
2203
+ children: /* @__PURE__ */ jsx(ResultField, { field: element, value: item, hideLabel: true })
2204
+ }
2205
+ ) }) : columns.map((column) => {
2206
+ const cell = typeof item === "object" && item !== null ? ownProp(item, column.name) : void 0;
2207
+ return /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 align-top", children: /* @__PURE__ */ jsx(
2208
+ "div",
2209
+ {
2210
+ className: cn(
2211
+ "max-w-[44ch]",
2212
+ column.kind === "list" && isInlineColumn(column) ? "min-w-[16ch]" : "truncate"
2213
+ ),
2214
+ ...typeof cell === "string" || typeof cell === "number" ? { title: String(cell) } : {},
2215
+ children: /* @__PURE__ */ jsx(LeafValue, { field: column, value: cell, compact: true })
2216
+ }
2217
+ ) }, column.name);
2218
+ })
2219
+ ] }) }, index);
2220
+ }) })
2221
+ ] })
2222
+ }
2223
+ )
2224
+ );
2225
+ }
2226
+ function CompositeValue({ members }) {
2227
+ return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: members.map((member) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2228
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[12px] font-semibold text-foreground", children: member.name }),
2229
+ /* @__PURE__ */ jsx(NativeValue, { value: member.value })
2230
+ ] }, member.name)) });
2231
+ }
2232
+ function NativeValue({ value }) {
2233
+ if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx(Absent, {});
2234
+ const html = readHtmlContent(value);
2235
+ if (html) return /* @__PURE__ */ jsx(HtmlPreview, { content: html });
2236
+ const date = readDateContent(value);
2237
+ if (date) return /* @__PURE__ */ jsx("span", { className: "text-[13px] text-foreground", children: formatDateContent(date) });
2238
+ const document2 = readDocumentContent(value);
2239
+ if (document2?.url !== void 0) {
2240
+ return /* @__PURE__ */ jsx(
2241
+ FileRef,
2242
+ {
2243
+ url: document2.url,
2244
+ ...document2.mimeType !== void 0 ? { mimeType: document2.mimeType } : {},
2245
+ ...document2.filename !== void 0 ? { filename: document2.filename } : {}
2246
+ }
2247
+ );
2248
+ }
2249
+ if (isRecord(value) && Array.isArray(value.items)) {
2250
+ const items = value.items;
2251
+ if (items.length === 0) return /* @__PURE__ */ jsx(Absent, {});
2252
+ return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60 overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3 py-1.5", children: /* @__PURE__ */ jsx(NativeValue, { value: item }) }, index)) });
2253
+ }
2254
+ if (isRecord(value)) {
2255
+ const text = value.text;
2256
+ if (typeof text === "string") {
2257
+ return text === "" ? /* @__PURE__ */ jsx(Absent, {}) : /* @__PURE__ */ jsx(Markdown, { text });
2258
+ }
2259
+ }
2260
+ if (typeof value === "string") return /* @__PURE__ */ jsx(Markdown, { text: value });
2261
+ if (typeof value !== "object") return /* @__PURE__ */ jsx(Scalar, { value });
2262
+ return /* @__PURE__ */ jsx(RawValue, { value, compact: false });
2263
+ }
2264
+ function isRecord(value) {
2265
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2266
+ }
2267
+ function ScalarLines({ field, items }) {
2268
+ return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60 overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3 py-1.5", children: /* @__PURE__ */ jsx(LeafValue, { field, value: item }) }, index)) });
2269
+ }
2270
+ function ImageGallery({ items }) {
2271
+ const anyViewable = items.some((item) => {
2272
+ const content = readImageContent(item);
2273
+ return content ? isViewableUrl(content.publicUrl) || isViewableUrl(content.url) : false;
2274
+ });
2275
+ if (!anyViewable) return /* @__PURE__ */ jsx(FileRows, { items, kind: "image" });
2276
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-2", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border bg-card/40", children: /* @__PURE__ */ jsx(ImageValue, { value: item, inGallery: true }) }, index)) });
2277
+ }
2278
+ function FileRows({ items, kind }) {
2279
+ return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border overflow-hidden rounded-lg border border-border", children: items.map((item, index) => /* @__PURE__ */ jsx("div", { className: "bg-card/40 px-3.5 py-2", children: kind === "document" ? /* @__PURE__ */ jsx(DocumentValue, { value: item }) : /* @__PURE__ */ jsx(ImageValue, { value: item, inRow: true }) }, index)) });
2280
+ }
2281
+ function tableColumns(item) {
2282
+ if (item.kind !== "object" || item.fields.length === 0) return void 0;
2283
+ return item.fields;
2284
+ }
2285
+ function ResultField({ field, value, depth = 0, hideLabel = false }) {
2286
+ const s = useFieldStrings();
2287
+ const unwrapped = unwrap(field, value);
2288
+ const header = hideLabel ? null : /* @__PURE__ */ jsx(Label2, { field, describe: true });
2289
+ const copyText = (field.kind === "text" || field.kind === "prose") && typeof unwrapped === "string" && unwrapped !== "" ? unwrapped : void 0;
2290
+ if (isNativeDateNode(field)) {
2291
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
2292
+ header,
2293
+ /* @__PURE__ */ jsx(DateValue, { value: unwrapped })
2294
+ ] });
2295
+ }
2296
+ if (isNativeHtmlNode(field)) {
2297
+ const content = readHtmlContent(unwrapped);
2298
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2299
+ header,
2300
+ content ? /* @__PURE__ */ jsx(HtmlPreview, { content }) : /* @__PURE__ */ jsx(Absent, {})
2301
+ ] });
2302
+ }
2303
+ if (isNativeCompositeNode(field)) {
2304
+ const members = readCompositeContent(unwrapped);
2305
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2306
+ header,
2307
+ members && members.length > 0 ? /* @__PURE__ */ jsx(CompositeValue, { members }) : /* @__PURE__ */ jsx(Absent, {})
2308
+ ] });
2309
+ }
2310
+ switch (field.kind) {
2311
+ case "object": {
2312
+ const memberValue = (child) => typeof unwrapped === "object" && unwrapped !== null ? ownProp(unwrapped, child.name) : void 0;
2313
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2314
+ header,
2315
+ /* @__PURE__ */ jsx(
2316
+ "div",
2317
+ {
2318
+ className: cn(
2319
+ // A definition grid, not a stack. Label above value spends two
2320
+ // lines on every field, so a structure of ten is twenty lines of
2321
+ // alternating label and answer with nothing aligned. Beside each
2322
+ // other they take one line and the values line up, which is what
2323
+ // makes a record scannable.
2324
+ //
2325
+ // The label column is CAPPED at 40%, and that is not cosmetic. A
2326
+ // bare `auto` sizes to the widest label in the grid - label text
2327
+ // plus its concept pill - so one long field name starved every
2328
+ // value beside it, and inside a 400px panel a paragraph came out
2329
+ // as a ten-line ribbon in a 130px column. `min-content` keeps a
2330
+ // short label short; the cap stops any label taking more than its
2331
+ // share, whatever the panel width turns out to be.
2332
+ "grid grid-cols-[minmax(min-content,40%)_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1.5",
2333
+ // A list item already sits in its own bordered row; nesting a
2334
+ // second card inside it draws a box in a box for no information
2335
+ // gained.
2336
+ !hideLabel && "rounded-lg border border-border bg-card/40 px-3.5 py-3"
2337
+ ),
2338
+ children: field.fields.map(
2339
+ (child) => (
2340
+ // Only a SHORT value shares a line. Prose needs the full width, and
2341
+ // so does anything with chrome of its own - a table, a gallery, a
2342
+ // frame - which beside a label would be squeezed into whatever the
2343
+ // longest label left over.
2344
+ isInlineColumn(child) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
2345
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx(Label2, { field: child, describe: true }) }),
2346
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 text-right [&_[data-chips]]:justify-end", children: /* @__PURE__ */ jsx(LeafValue, { field: child, value: memberValue(child) }) })
2347
+ ] }, child.name) : /* @__PURE__ */ jsx("div", { className: "col-span-2 min-w-0", children: /* @__PURE__ */ jsx(ResultField, { field: child, value: memberValue(child), depth: depth + 1 }) }, child.name)
2348
+ )
2349
+ )
2350
+ }
2351
+ )
2352
+ ] });
2353
+ }
2354
+ case "list": {
2355
+ const items = Array.isArray(unwrapped) ? unwrapped : [];
2356
+ const columns = tableColumns(field.item);
2357
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2358
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
2359
+ header,
2360
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: s.itemsCount(items.length) })
2361
+ ] }),
2362
+ items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-[13px] italic text-muted-foreground", children: s.noItemsYet }) : columns ? (
2363
+ // Same shape every row: the labels are column headers, not per-row
2364
+ // labels. See `ObjectTable`.
2365
+ /* @__PURE__ */ jsx(
2366
+ ObjectTable,
2367
+ {
2368
+ columns,
2369
+ element: field.item,
2370
+ items,
2371
+ label: field.title ?? humanizeFieldName(field.name)
2372
+ }
2373
+ )
2374
+ ) : TABULAR_KINDS.has(field.item.kind) ? /* @__PURE__ */ jsx(ScalarChips, { field: field.item, items }) : field.item.kind === "prose" || field.item.kind === "unknown" ? (
2375
+ // `unknown` shares the LINES layout, and that is a reading of the
2376
+ // standard rather than a convenience. `unknown` is the deliberate
2377
+ // escape hatch: the producer could not map the node honestly and
2378
+ // said so, which is what an untyped `type = "list"` field derives
2379
+ // to. A card per entry would spend a bordered box and an index on
2380
+ // each value to say nothing extra - the card earns its place around
2381
+ // a STRUCTURE, and this is precisely the node we have been told is
2382
+ // not known to be one. Lines cost nothing and read the same whether
2383
+ // the entries turn out to be words or objects, because `LeafValue`
2384
+ // falls through to `RawValue` either way.
2385
+ /* @__PURE__ */ jsx(ScalarLines, { field: field.item, items })
2386
+ ) : field.item.kind === "image" ? /* @__PURE__ */ jsx(ImageGallery, { items }) : field.item.kind === "document" ? /* @__PURE__ */ jsx(FileRows, { items, kind: "document" }) : (
2387
+ // Nested, or carrying prose: a card per entry is what a table cell
2388
+ // cannot hold. The index label earns its place here, where the
2389
+ // entries are structures rather than values.
2390
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: items.map((item, index) => /* @__PURE__ */ jsxs(
2391
+ "div",
2392
+ {
2393
+ className: "space-y-2 rounded-lg border border-border bg-card/40 px-3.5 py-3",
2394
+ children: [
2395
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground", children: index + 1 }),
2396
+ /* @__PURE__ */ jsx(ResultField, { field: field.item, value: item, depth: depth + 1, hideLabel: true })
2397
+ ]
2398
+ },
2399
+ index
2400
+ )) })
2401
+ )
2402
+ ] });
2403
+ }
2404
+ case "boolean":
2405
+ case "date":
2406
+ case "document":
2407
+ case "image":
2408
+ case "text":
2409
+ case "prose":
2410
+ case "number":
2411
+ case "enum":
2412
+ case "unknown":
2413
+ return /* @__PURE__ */ jsxs("div", { className: field.kind === "image" ? "space-y-1.5" : "space-y-0.5", children: [
2414
+ copyText === void 0 ? header : /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2415
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header }),
2416
+ /* @__PURE__ */ jsx(CopyButton, { value: copyText, label: s.copyText })
2417
+ ] }),
2418
+ /* @__PURE__ */ jsx(LeafValue, { field, value })
2419
+ ] });
2420
+ }
2421
+ return null;
2422
+ }
2423
+
2424
+ // src/react/download-stuff.ts
2425
+ function saveBlob(blob, filename) {
2426
+ const href = URL.createObjectURL(blob);
2427
+ const anchor = document.createElement("a");
2428
+ anchor.href = href;
2429
+ anchor.download = filename;
2430
+ document.body.appendChild(anchor);
2431
+ anchor.click();
2432
+ anchor.remove();
2433
+ setTimeout(() => URL.revokeObjectURL(href), 0);
2434
+ }
2435
+ function mimeForExtension(extension) {
2436
+ return extension === "html" ? "text/html;charset=utf-8" : "text/plain;charset=utf-8";
2437
+ }
2438
+ function extensionOf(url, filename) {
2439
+ const named = filename?.match(/\.([A-Za-z0-9]{1,8})$/)?.[1];
2440
+ if (named) return named.toLowerCase();
2441
+ const path = url.split(/[?#]/)[0] ?? url;
2442
+ const match = path.match(/\.([A-Za-z0-9]{1,8})$/)?.[1];
2443
+ return match ? match.toLowerCase() : "";
2444
+ }
2445
+ function fileNameFor(file, base) {
2446
+ if (file.filename) return file.filename;
2447
+ const extension = file.extension ?? extensionOf(file.url ?? "", file.filename);
2448
+ const stem = `${base}-${file.path.replace(/\./g, "-")}`;
2449
+ return extension ? `${stem}.${extension}` : stem;
2450
+ }
2451
+ async function downloadStuff({
2452
+ field,
2453
+ value,
2454
+ baseName,
2455
+ resolveUrl
2456
+ }) {
2457
+ const files = collectStuffFiles(field, value);
2458
+ const isBareFile = files.length === 1 && files[0]?.path === field.name;
2459
+ for (const file of files) {
2460
+ const name = fileNameFor(file, baseName);
2461
+ if (file.text !== void 0) {
2462
+ saveBlob(new Blob([file.text], { type: mimeForExtension(file.extension) }), name);
2463
+ continue;
2464
+ }
2465
+ const href = resolveUrl?.(file.url ?? "") ?? file.publicUrl ?? file.url;
2466
+ if (!href) continue;
2467
+ try {
2468
+ const response = await fetch(href);
2469
+ if (!response.ok) throw new Error(String(response.status));
2470
+ saveBlob(await response.blob(), name);
2471
+ } catch {
2472
+ window.open(href, "_blank", "noopener");
2473
+ }
2474
+ }
2475
+ if (isBareFile) return;
2476
+ saveBlob(
2477
+ new Blob([JSON.stringify(value, null, 2)], { type: "application/json;charset=utf-8" }),
2478
+ `${baseName}.json`
2479
+ );
2480
+ }
2481
+ var JSON_TOKEN_RE = /("(?:\\.|[^"\\])*"\s*:)|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
2482
+ function highlight(json) {
2483
+ const out = [];
2484
+ let last = 0;
2485
+ for (const match of json.matchAll(JSON_TOKEN_RE)) {
2486
+ const [token, key, str, literal] = match;
2487
+ const at = match.index;
2488
+ if (at > last) {
2489
+ out.push(
2490
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: json.slice(last, at) }, `p${last}`)
2491
+ );
2492
+ }
2493
+ out.push(
2494
+ /* @__PURE__ */ jsx(
2495
+ "span",
2496
+ {
2497
+ className: key ? "text-muted-foreground" : str ? "text-foreground" : "font-semibold text-foreground",
2498
+ children: token
2499
+ },
2500
+ at
2501
+ )
2502
+ );
2503
+ last = at + token.length;
2504
+ }
2505
+ if (last < json.length) {
2506
+ out.push(
2507
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: json.slice(last) }, "tail")
2508
+ );
2509
+ }
2510
+ return out;
2511
+ }
2512
+ function JsonView({ value }) {
2513
+ const s = useFieldStrings();
2514
+ const [copied, setCopied] = useState(false);
2515
+ const text = value === null || value === void 0 ? void 0 : typeof value === "object" ? stringifyValue(value) : JSON.stringify(value);
2516
+ if (text === void 0) {
2517
+ return /* @__PURE__ */ jsx(Absent, {});
2518
+ }
2519
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2520
+ typeof navigator !== "undefined" && navigator.clipboard && /* @__PURE__ */ jsx(
2521
+ "button",
2522
+ {
2523
+ type: "button",
2524
+ onClick: () => {
2525
+ void navigator.clipboard.writeText(text).then(() => {
2526
+ setCopied(true);
2527
+ window.setTimeout(() => setCopied(false), 1500);
2528
+ });
2529
+ },
2530
+ "aria-label": s.copyJson,
2531
+ className: "absolute right-2 top-2 rounded border border-border bg-card p-1 text-muted-foreground hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1",
2532
+ children: copied ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Copy, { "aria-hidden": true, className: "size-3.5" })
2533
+ }
2534
+ ),
2535
+ /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-lg border border-border bg-card/40 px-3.5 py-3 pr-12 font-mono text-[12px] leading-relaxed", children: highlight(text) })
2536
+ ] });
2537
+ }
2538
+ function StuffViewer({
2539
+ field,
2540
+ value,
2541
+ name,
2542
+ defaultView = "rendered",
2543
+ downloadBaseName,
2544
+ hideDownload = false,
2545
+ className
2546
+ }) {
2547
+ const s = useFieldStrings();
2548
+ const named = name ? { ...field, name } : field;
2549
+ const [view, setView] = useState(defaultView);
2550
+ const [saving, setSaving] = useState(false);
2551
+ const resolveUrl = useResolveUrl();
2552
+ const handleDownload = useCallback(async () => {
2553
+ setSaving(true);
2554
+ try {
2555
+ await downloadStuff({
2556
+ field,
2557
+ value,
2558
+ baseName: downloadBaseName ?? named.name ?? "result",
2559
+ resolveUrl
2560
+ });
2561
+ } finally {
2562
+ setSaving(false);
2563
+ }
2564
+ }, [field, named.name, value, downloadBaseName, resolveUrl]);
2565
+ const views = [
2566
+ { id: "rendered", label: s.viewRendered },
2567
+ { id: "json", label: s.viewJson }
2568
+ ];
2569
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
2570
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-x-4 gap-y-1", children: [
2571
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 space-y-1", children: /* @__PURE__ */ jsx(ResultHeader, { field: named }) }),
2572
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
2573
+ !hideDownload && /* @__PURE__ */ jsxs(
2574
+ "button",
2575
+ {
2576
+ type: "button",
2577
+ onClick: () => void handleDownload(),
2578
+ disabled: saving,
2579
+ "aria-label": s.download,
2580
+ className: "inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-[12px] text-muted-foreground hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-60",
2581
+ children: [
2582
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }),
2583
+ saving ? s.downloading : s.download
2584
+ ]
2585
+ }
2586
+ ),
2587
+ /* @__PURE__ */ jsx(
2588
+ "div",
2589
+ {
2590
+ role: "group",
2591
+ "aria-label": s.resultViewGroup,
2592
+ className: "flex rounded-md border border-border p-0.5",
2593
+ children: views.map(({ id, label }) => /* @__PURE__ */ jsx(
2594
+ "button",
2595
+ {
2596
+ type: "button",
2597
+ onClick: () => setView(id),
2598
+ "aria-pressed": view === id,
2599
+ className: cn(
2600
+ "rounded px-2 py-0.5 text-[12px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1",
2601
+ view === id ? "bg-card font-medium text-foreground" : "text-muted-foreground hover:text-foreground"
2602
+ ),
2603
+ children: label
2604
+ },
2605
+ id
2606
+ ))
2607
+ }
2608
+ )
2609
+ ] })
2610
+ ] }),
2611
+ view === "json" ? /* @__PURE__ */ jsx(JsonView, { value }) : /* @__PURE__ */ jsx(ResultField, { field, value, hideLabel: true })
2612
+ ] });
2613
+ }
1268
2614
 
1269
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings };
2615
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, StuffViewer, TextField, UnknownField, downloadStuff, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings, useResolveShareUrl, useResolveUrl, useResolvedUrl };
1270
2616
  //# sourceMappingURL=index.js.map
1271
2617
  //# sourceMappingURL=index.js.map