@pipelex/mthds-form 0.9.0 → 0.10.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 (50) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-S4CB423P.js → chunk-4JQAJT7C.js} +3 -3
  3. package/dist/{chunk-S4CB423P.js.map → chunk-4JQAJT7C.js.map} +1 -1
  4. package/dist/{chunk-YABRYTQ4.js → chunk-4KGQ65TQ.js} +3 -3
  5. package/dist/{chunk-YABRYTQ4.js.map → chunk-4KGQ65TQ.js.map} +1 -1
  6. package/dist/chunk-CAKQ5VFT.js +35 -0
  7. package/dist/chunk-CAKQ5VFT.js.map +1 -0
  8. package/dist/{chunk-25I6JDIK.js → chunk-FABY3DEY.js} +2 -2
  9. package/dist/chunk-FABY3DEY.js.map +1 -0
  10. package/dist/{chunk-KG4IYFWI.js → chunk-G6BWZUGV.js} +165 -46
  11. package/dist/chunk-G6BWZUGV.js.map +1 -0
  12. package/dist/{chunk-AZZAAGNX.js → chunk-TID7ZN63.js} +7 -3
  13. package/dist/chunk-TID7ZN63.js.map +1 -0
  14. package/dist/chunk-XCO6GTEC.js +38 -0
  15. package/dist/chunk-XCO6GTEC.js.map +1 -0
  16. package/dist/{chunk-KIEOLP2L.js → chunk-ZB4RYUKV.js} +4 -4
  17. package/dist/chunk-ZB4RYUKV.js.map +1 -0
  18. package/dist/core/derive.d.ts +1 -0
  19. package/dist/core/derive.js +2 -2
  20. package/dist/core/descriptor.d.ts +13 -2
  21. package/dist/core/descriptor.js +1 -1
  22. package/dist/core/file-formats.d.ts +34 -7
  23. package/dist/core/file-formats.js +1 -1
  24. package/dist/core/gating-fields.d.ts +1 -0
  25. package/dist/core/index.d.ts +2 -1
  26. package/dist/core/index.js +7 -6
  27. package/dist/core/narrow-file-formats.d.ts +50 -0
  28. package/dist/core/narrow-file-formats.js +4 -0
  29. package/dist/core/narrow-file-formats.js.map +1 -0
  30. package/dist/core/native-content.d.ts +20 -11
  31. package/dist/core/native-content.js +1 -1
  32. package/dist/core/readiness.d.ts +1 -0
  33. package/dist/core/stuff-files.d.ts +1 -0
  34. package/dist/core/stuff-files.js +2 -2
  35. package/dist/core/values.d.ts +1 -0
  36. package/dist/core/values.js +3 -3
  37. package/dist/generative/index.d.ts +105 -12
  38. package/dist/generative/index.js +79 -157
  39. package/dist/generative/index.js.map +1 -1
  40. package/dist/{ui-designer.mthds → layout-design.mthds} +177 -29
  41. package/dist/react/index.d.ts +66 -10
  42. package/dist/react/index.js +5 -5
  43. package/dist/styles.css +1 -1
  44. package/package.json +3 -3
  45. package/dist/chunk-25I6JDIK.js.map +0 -1
  46. package/dist/chunk-2JWRG53S.js +0 -33
  47. package/dist/chunk-2JWRG53S.js.map +0 -1
  48. package/dist/chunk-AZZAAGNX.js.map +0 -1
  49. package/dist/chunk-KG4IYFWI.js.map +0 -1
  50. package/dist/chunk-KIEOLP2L.js.map +0 -1
@@ -1,7 +1,7 @@
1
- import { collectStuffFiles } from './chunk-S4CB423P.js';
2
- import { viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, readDataUrl, readDateContent, formatDateContent, readImageContent, readDocumentContent } from './chunk-AZZAAGNX.js';
3
- import { conceptCategory } from './chunk-25I6JDIK.js';
4
- import { isAcceptedFile, acceptMapForKind, DOCUMENT_FORMATS, IMAGE_FORMATS } from './chunk-2JWRG53S.js';
1
+ import { collectStuffFiles } from './chunk-4JQAJT7C.js';
2
+ import { viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isNativeValueNode, readDateContent, formatDateContent, readImageContent, readDataUrl, readDocumentContent } from './chunk-TID7ZN63.js';
3
+ import { conceptCategory } from './chunk-FABY3DEY.js';
4
+ import { acceptLabel, acceptMap, isAcceptedFile, formatOfFilename, DOCUMENT_FORMATS, IMAGE_FORMATS } from './chunk-XCO6GTEC.js';
5
5
  import { isFilled } from './chunk-PM55AHRO.js';
6
6
  import { toStoredDateValue, toDateInputValue } from './chunk-GKV77RAS.js';
7
7
  import { ownProp } from './chunk-V3OV2XQT.js';
@@ -66,6 +66,39 @@ function humanizeFieldName(name) {
66
66
  if (words === "") return name;
67
67
  return words.charAt(0).toUpperCase() + words.slice(1);
68
68
  }
69
+ function humanizeEnumValue(code) {
70
+ if (!ENUM_CODE.test(code)) return code;
71
+ const allCaps = code === code.toUpperCase() && code !== code.toLowerCase();
72
+ if (!allCaps) return humanizeFieldName(code);
73
+ return CODE_SEPARATOR.test(code) ? humanizeFieldName(code.toLowerCase()) : code;
74
+ }
75
+ var ENUM_CODE = /^\p{L}[\p{L}\p{N}_-]*$/u;
76
+ var CODE_SEPARATOR = /[_-]/;
77
+ function enumLabeler(options, presentation) {
78
+ if (presentation !== "app") return asWritten;
79
+ let labeler = APP_LABELERS.get(options);
80
+ if (labeler === void 0) {
81
+ labeler = wordsOrCodes(options);
82
+ APP_LABELERS.set(options, labeler);
83
+ }
84
+ return labeler;
85
+ }
86
+ function asWritten(value) {
87
+ return value;
88
+ }
89
+ function wordsOrCodes(options) {
90
+ const labels = /* @__PURE__ */ new Map();
91
+ const owners = /* @__PURE__ */ new Map();
92
+ for (const option of options) {
93
+ const label = humanizeEnumValue(option);
94
+ const owner = owners.get(label);
95
+ if (owner !== void 0 && owner !== option) return asWritten;
96
+ owners.set(label, option);
97
+ labels.set(option, label);
98
+ }
99
+ return (value) => labels.get(value) ?? value;
100
+ }
101
+ var APP_LABELERS = /* @__PURE__ */ new WeakMap();
69
102
  function fieldLabel(title, name, presentation) {
70
103
  if (title !== void 0) return title;
71
104
  return presentation === "app" ? humanizeFieldName(name) : name;
@@ -170,8 +203,9 @@ var DEFAULT_FIELD_STRINGS = {
170
203
  dropToUpload: "Drop to upload",
171
204
  dropOrBrowse: "Drop a file or click to browse",
172
205
  pasteUrlInstead: "paste a URL instead",
173
- urlPlaceholder: "https://\u2026 or pipelex-storage://\u2026",
174
- uploadedFile: "Uploaded file",
206
+ urlPlaceholder: "https://\u2026",
207
+ fileUrlAria: (label) => label ? `Link to the file for ${label}` : "Link to the file",
208
+ uploadedFile: "Attached file",
175
209
  encodedFileSummary: (format, bytes) => `${format} \xB7 ${formatBytes(bytes)}`,
176
210
  preview: "Preview",
177
211
  removeFileAria: "Remove file",
@@ -192,7 +226,7 @@ var DEFAULT_FIELD_STRINGS = {
192
226
  resultViewGroup: "Result view",
193
227
  yes: "Yes",
194
228
  no: "No",
195
- unsupportedFileType: (accept) => `That file type is not supported. Accepted formats: ${accept}.`,
229
+ unsupportedFileType: (formats) => `That file type is not supported. Accepted formats: ${formats}.`,
196
230
  hideOptionalFields: "Hide optional fields",
197
231
  hideOptionalInputs: "Hide optional inputs",
198
232
  optionalFieldsCount: (count) => count === 1 ? "1 optional field" : `${count} optional fields`,
@@ -296,6 +330,11 @@ function encodedFileSummary(url, strings) {
296
330
  var IMAGE_EXT_RE = /\.(jpg|jpeg|png|gif|webp|bmp|svg|avif)(\?|$)/i;
297
331
  var PDF_EXT_RE = /\.pdf(\?|$)/i;
298
332
  var DATA_URL_MIME_RE = /^data:([^;,]+)/i;
333
+ var WEB_URL_RE = /^\s*https?:\/\//i;
334
+ var URL_SCHEME_RE = /^\s*[a-z][a-z0-9+.-]*:/i;
335
+ function isReadableLink(url) {
336
+ return WEB_URL_RE.test(url) || !URL_SCHEME_RE.test(url);
337
+ }
299
338
  function dataUrlMime(url) {
300
339
  return DATA_URL_MIME_RE.exec(url)?.[1]?.toLowerCase();
301
340
  }
@@ -318,8 +357,10 @@ function FileField({
318
357
  category
319
358
  }) {
320
359
  const s = useFieldStrings();
360
+ const presentation = useFieldPresentation();
321
361
  const domId = useFieldDomId(id);
322
362
  const [showUrl, setShowUrl] = useState(false);
363
+ const [typedUrl, setTypedUrl] = useState(null);
323
364
  const [previewOpen, setPreviewOpen] = useState(false);
324
365
  const [localPreview, setLocalPreview] = useState(null);
325
366
  const [resolved, setResolved] = useState(null);
@@ -331,9 +372,12 @@ function FileField({
331
372
  }, []);
332
373
  useEffect(() => () => setLocal(null), [setLocal]);
333
374
  const [rejected, setRejected] = useState(null);
375
+ const formats = field.formats;
376
+ const hint = acceptLabel(formats);
377
+ const accept = useMemo(() => acceptMap(formats), [formats]);
334
378
  const handleFile = useCallback(
335
379
  (file) => {
336
- if (!isAcceptedFile(field.kind, file)) {
380
+ if (!isAcceptedFile(formats, file)) {
337
381
  setRejected(file.name);
338
382
  return;
339
383
  }
@@ -341,7 +385,7 @@ function FileField({
341
385
  setLocal({ objectUrl: URL.createObjectURL(file), type: file.type });
342
386
  onDropFile(file);
343
387
  },
344
- [field.kind, setLocal, onDropFile]
388
+ [formats, setLocal, onDropFile]
345
389
  );
346
390
  const busy = disabled || uploading;
347
391
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
@@ -353,7 +397,7 @@ function FileField({
353
397
  // entirely, and a browser reporting an empty `File.type` slips through
354
398
  // react-dropzone's own matcher. `handleFile` is where the answer is
355
399
  // decided; this is the affordance in front of it.
356
- accept: acceptMapForKind(field.kind),
400
+ accept,
357
401
  // The tab stop belongs on the INPUT, not on this div - see the root element
358
402
  // below. Without this, react-dropzone puts `tabIndex: 0` and its own key
359
403
  // handlers on a `role="presentation"` div, and the element a keyboard or
@@ -414,6 +458,8 @@ function FileField({
414
458
  const resolvedSrc = resolved && resolved.uri === storageUri ? viewableUrl(resolved.src) : void 0;
415
459
  const previewSrc = (localIsCurrent ? localPreview?.objectUrl : void 0) ?? directSrc ?? resolvedSrc;
416
460
  const previewPending = !previewSrc && !!storageUri && resolved?.uri !== storageUri;
461
+ const urlValue = value?.url ?? "";
462
+ const urlText = presentation === "studio" || isReadableLink(urlValue) || urlValue === typedUrl ? urlValue : "";
417
463
  return /* @__PURE__ */ jsxs(
418
464
  FieldShell,
419
465
  {
@@ -431,6 +477,8 @@ function FileField({
431
477
  FileChip,
432
478
  {
433
479
  value,
480
+ formats,
481
+ presentation,
434
482
  disabled,
435
483
  onClear: clear,
436
484
  canPreview,
@@ -480,7 +528,7 @@ function FileField({
480
528
  /* @__PURE__ */ jsx(Upload, { className: "h-5 w-5 text-muted-foreground", strokeWidth: 1.75 }),
481
529
  /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
482
530
  /* @__PURE__ */ jsx("p", { className: "text-[13px] text-foreground", children: isDragActive ? s.dropToUpload : s.dropOrBrowse }),
483
- field.accept && /* @__PURE__ */ jsx("p", { className: "font-mono text-[10.5px] text-muted-foreground", children: field.accept })
531
+ hint && /* @__PURE__ */ jsx("p", { className: "font-mono text-[10.5px] text-muted-foreground", children: hint })
484
532
  ] })
485
533
  ] })
486
534
  ]
@@ -489,9 +537,8 @@ function FileField({
489
537
  ),
490
538
  rejected && !busy && /* @__PURE__ */ jsxs("p", { role: "alert", className: "text-[12px] text-destructive", children: [
491
539
  /* @__PURE__ */ jsx("span", { className: "font-mono", children: rejected }),
492
- " \u2014",
493
- " ",
494
- s.unsupportedFileType(field.accept ?? "")
540
+ " \u2014 ",
541
+ s.unsupportedFileType(hint)
495
542
  ] }),
496
543
  !showUrl ? /* @__PURE__ */ jsxs(
497
544
  "button",
@@ -510,11 +557,13 @@ function FileField({
510
557
  {
511
558
  type: "text",
512
559
  autoFocus: true,
513
- value: value?.url ?? "",
560
+ value: urlText,
514
561
  disabled: busy,
562
+ "aria-label": s.fileUrlAria(fieldLabel(field.title, field.name, presentation)),
515
563
  placeholder: s.urlPlaceholder,
516
564
  onChange: (e) => {
517
565
  setLocal(null);
566
+ setTypedUrl(e.target.value);
518
567
  onChange(e.target.value ? { url: e.target.value } : void 0);
519
568
  },
520
569
  className: cn(fieldControlClass, "h-9 px-3 font-mono text-[12px]")
@@ -556,8 +605,17 @@ function PdfPreview({ src }) {
556
605
  }
557
606
  );
558
607
  }
608
+ function fileChipSubtitle(url, filename, formats, presentation, strings) {
609
+ if (url === void 0) return void 0;
610
+ const summary = encodedFileSummary(url, strings);
611
+ if (summary !== void 0) return summary;
612
+ if (presentation === "studio" || isReadableLink(url)) return url;
613
+ return filename ? formatOfFilename(formats, filename)?.label : void 0;
614
+ }
559
615
  function FileChip({
560
616
  value,
617
+ formats,
618
+ presentation,
561
619
  disabled,
562
620
  onClear,
563
621
  canPreview,
@@ -566,15 +624,16 @@ function FileChip({
566
624
  }) {
567
625
  const s = useFieldStrings();
568
626
  const url = value?.url;
627
+ const filename = value?.filename;
569
628
  const subtitle = useMemo(
570
- () => url === void 0 ? void 0 : encodedFileSummary(url, s) ?? url,
571
- [url, s]
629
+ () => fileChipSubtitle(url, filename, formats, presentation, s),
630
+ [url, filename, formats, presentation, s]
572
631
  );
573
632
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-md border border-border bg-input px-3 py-2.5", children: [
574
633
  /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted", children: /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 text-muted-foreground" }) }),
575
634
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
576
635
  /* @__PURE__ */ jsx("p", { className: "truncate text-[13px] font-medium text-foreground", children: value?.filename ?? s.uploadedFile }),
577
- /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[10.5px] text-muted-foreground", children: subtitle })
636
+ subtitle && /* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[10.5px] text-muted-foreground", children: subtitle })
578
637
  ] }),
579
638
  canPreview && /* @__PURE__ */ jsxs(
580
639
  "button",
@@ -773,7 +832,9 @@ ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
773
832
  var CLEAR_VALUE = "__none__";
774
833
  function EnumField({ field, value, onChange, id, error, disabled }) {
775
834
  const s = useFieldStrings();
776
- const isSegmented = field.options.length <= 4 && field.options.every((o) => o.length <= 16);
835
+ const presentation = useFieldPresentation();
836
+ const labelOf = enumLabeler(field.options, presentation);
837
+ const isSegmented = field.options.length <= 4 && field.options.every((o) => labelOf(o).length <= 16);
777
838
  const domId = useFieldDomId(id);
778
839
  return /* @__PURE__ */ jsx(
779
840
  FieldShell,
@@ -797,7 +858,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
797
858
  value: value ?? "",
798
859
  onValueChange: (next) => onChange(next === "" ? void 0 : next),
799
860
  disabled,
800
- "aria-label": field.title ?? field.name,
861
+ "aria-label": fieldLabel(field.title, field.name, presentation),
801
862
  className: "flex flex-wrap justify-start gap-1.5",
802
863
  children: field.options.map((option) => /* @__PURE__ */ jsxs(
803
864
  ToggleGroupItem,
@@ -807,7 +868,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
807
868
  className: "h-9 min-w-0 gap-1.5 border-border bg-input px-3 text-[13px] text-muted-foreground hover:bg-muted hover:text-foreground data-[state=on]:border-primary/40 data-[state=on]:bg-primary/10 data-[state=on]:font-medium data-[state=on]:text-foreground",
808
869
  children: [
809
870
  value === option && /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-primary", strokeWidth: 2.5, "aria-hidden": "true" }),
810
- option
871
+ labelOf(option)
811
872
  ]
812
873
  },
813
874
  option
@@ -824,7 +885,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
824
885
  /* @__PURE__ */ jsx(SelectTrigger, { id: domId, "aria-invalid": !!error, className: "h-11", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: s.selectPlaceholder }) }),
825
886
  /* @__PURE__ */ jsxs(SelectContent, { children: [
826
887
  /* @__PURE__ */ jsx(SelectItem, { value: CLEAR_VALUE, children: s.selectPlaceholder }),
827
- field.options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option, children: option }, option))
888
+ field.options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option, children: labelOf(option) }, option))
828
889
  ] })
829
890
  ]
830
891
  }
@@ -1345,11 +1406,12 @@ function ResultEnvProvider({
1345
1406
  resolveUrl,
1346
1407
  resolveShareUrl,
1347
1408
  proseImages,
1409
+ tableColumns,
1348
1410
  children
1349
1411
  }) {
1350
1412
  const env = useMemo(
1351
- () => ({ resolveUrl, resolveShareUrl, proseImages }),
1352
- [resolveUrl, resolveShareUrl, proseImages]
1413
+ () => ({ resolveUrl, resolveShareUrl, proseImages, tableColumns }),
1414
+ [resolveUrl, resolveShareUrl, proseImages, tableColumns]
1353
1415
  );
1354
1416
  return /* @__PURE__ */ jsx(ResultEnvContext, { value: env, children });
1355
1417
  }
@@ -1365,6 +1427,12 @@ function useResolveShareUrl() {
1365
1427
  function useProseImages() {
1366
1428
  return use(ResultEnvContext).proseImages ?? "link";
1367
1429
  }
1430
+ var DEFAULT_TABLE_COLUMNS = 5;
1431
+ function useTableColumns() {
1432
+ const budget = use(ResultEnvContext).tableColumns;
1433
+ if (budget === void 0 || Number.isNaN(budget)) return DEFAULT_TABLE_COLUMNS;
1434
+ return Math.max(1, Math.floor(budget));
1435
+ }
1368
1436
  var TooltipProvider = TooltipPrimitive.Provider;
1369
1437
  var TooltipRoot = TooltipPrimitive.Root;
1370
1438
  var TooltipTrigger = TooltipPrimitive.Trigger;
@@ -1811,6 +1879,18 @@ function DateValue({ value }) {
1811
1879
  if (!content) return /* @__PURE__ */ jsx(Absent, {});
1812
1880
  return /* @__PURE__ */ jsx("span", { className: "text-[13px] text-foreground", children: formatDateContent(content) });
1813
1881
  }
1882
+ function EnumValue({
1883
+ field,
1884
+ value,
1885
+ compact
1886
+ }) {
1887
+ const presentation = useFieldPresentation();
1888
+ const shown = typeof value === "string" ? enumLabeler(field.options, presentation)(value) : value;
1889
+ return /* @__PURE__ */ jsx(Scalar, { value: shown, compact });
1890
+ }
1891
+ function cellTitle(column, cell, presentation) {
1892
+ return column.kind === "enum" && typeof cell === "string" ? enumLabeler(column.options, presentation)(cell) : String(cell);
1893
+ }
1814
1894
  function fileLabel(url, filename, strings) {
1815
1895
  if (filename) return filename;
1816
1896
  const encoded = encodedFileSummary(url, strings);
@@ -1829,16 +1909,18 @@ function CopyButton({
1829
1909
  title
1830
1910
  }) {
1831
1911
  const [copied, setCopied] = useState(false);
1912
+ useEffect(() => {
1913
+ if (!copied) return;
1914
+ const timer = window.setTimeout(() => setCopied(false), 1500);
1915
+ return () => window.clearTimeout(timer);
1916
+ }, [copied]);
1832
1917
  if (typeof navigator === "undefined" || !navigator.clipboard) return null;
1833
1918
  return /* @__PURE__ */ jsx(
1834
1919
  "button",
1835
1920
  {
1836
1921
  type: "button",
1837
1922
  onClick: () => {
1838
- const done = () => {
1839
- setCopied(true);
1840
- window.setTimeout(() => setCopied(false), 1500);
1841
- };
1923
+ const done = () => setCopied(true);
1842
1924
  if (!provide) {
1843
1925
  void navigator.clipboard.writeText(value ?? "").then(done);
1844
1926
  return;
@@ -2118,6 +2200,8 @@ function LeafValue({
2118
2200
  return /* @__PURE__ */ jsx(ImageValue, { value, compact });
2119
2201
  case "object":
2120
2202
  return compact ? /* @__PURE__ */ jsx(RecordSummary, { field, value }) : /* @__PURE__ */ jsx(Scalar, { value });
2203
+ case "enum":
2204
+ return /* @__PURE__ */ jsx(EnumValue, { field, value, compact });
2121
2205
  default:
2122
2206
  return /* @__PURE__ */ jsx(Scalar, { value, compact });
2123
2207
  }
@@ -2126,7 +2210,7 @@ function RecordSummary({ field, value }) {
2126
2210
  const s = useFieldStrings();
2127
2211
  if (isNativeDateNode(field)) return /* @__PURE__ */ jsx(DateValue, { value });
2128
2212
  if (!isRecord(value) || Object.keys(value).length === 0) return /* @__PURE__ */ jsx(Absent, {});
2129
- const naming = isNativeHtmlNode(field) ? void 0 : field.fields.find((member) => member.kind === "text") ?? field.fields.find((member) => member.kind === "prose");
2213
+ const naming = isNativeHtmlNode(field) ? void 0 : recordNameField(field.fields) ?? field.fields.find((member) => member.kind === "prose");
2130
2214
  if (naming === void 0) {
2131
2215
  return /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-[12.5px] text-muted-foreground", children: s.fieldsCount(field.fields.length) });
2132
2216
  }
@@ -2199,8 +2283,10 @@ function fitsACellWhole(field) {
2199
2283
  return true;
2200
2284
  case "text":
2201
2285
  return field.maxLength !== void 0 && field.maxLength <= CELL_LENGTH_LIMIT;
2286
+ case "object":
2287
+ return isNativeDateNode(field);
2202
2288
  case "list":
2203
- return fitsACellWhole(field.item);
2289
+ return isInlineColumn(field) && fitsACellWhole(field.item);
2204
2290
  default:
2205
2291
  return false;
2206
2292
  }
@@ -2213,10 +2299,12 @@ function ObjectTable({
2213
2299
  }) {
2214
2300
  const s = useFieldStrings();
2215
2301
  const presentation = useFieldPresentation();
2302
+ const budget = useTableColumns();
2216
2303
  const scrollerRef = useRef(null);
2217
2304
  const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
2218
2305
  const [viewportWidth, setViewportWidth] = useState();
2219
- const canExpand = columns.some((column) => !fitsACellWhole(column));
2306
+ const { shown, hidden, name } = chooseColumns(columns, budget);
2307
+ const canExpand = hidden > 0 || shown.some((column) => column !== name && !fitsACellWhole(column));
2220
2308
  useEffect(() => {
2221
2309
  const scroller = scrollerRef.current;
2222
2310
  if (!scroller) return;
@@ -2261,7 +2349,7 @@ function ObjectTable({
2261
2349
  children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-full border-collapse text-[13px]", children: [
2262
2350
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border bg-muted text-left", children: [
2263
2351
  canExpand && /* @__PURE__ */ jsx("th", { scope: "col", style: { width: EXPAND_COLUMN }, className: "px-1", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: s.rowDetailsColumn }) }),
2264
- columns.map((column) => /* @__PURE__ */ jsx(
2352
+ shown.map((column) => /* @__PURE__ */ jsx(
2265
2353
  "th",
2266
2354
  {
2267
2355
  scope: "col",
@@ -2275,7 +2363,7 @@ function ObjectTable({
2275
2363
  ))
2276
2364
  ] }) }),
2277
2365
  /* @__PURE__ */ jsx("tbody", { children: items.map((item, index) => {
2278
- const isOpen = expanded.has(index);
2366
+ const isOpen = canExpand && expanded.has(index);
2279
2367
  return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border/60 last:border-b-0", children: [
2280
2368
  canExpand && /* @__PURE__ */ jsx("td", { className: "px-1 pt-px align-top", children: /* @__PURE__ */ jsx(
2281
2369
  "button",
@@ -2288,7 +2376,7 @@ function ObjectTable({
2288
2376
  children: isOpen ? /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true, className: "size-3.5" })
2289
2377
  }
2290
2378
  ) }),
2291
- isOpen ? /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: "bg-card/40 p-0", children: /* @__PURE__ */ jsx(
2379
+ isOpen ? /* @__PURE__ */ jsx("td", { colSpan: shown.length, className: "bg-card/40 p-0", children: /* @__PURE__ */ jsx(
2292
2380
  "div",
2293
2381
  {
2294
2382
  className: "sticky left-0 px-3.5 py-3",
@@ -2303,16 +2391,16 @@ function ObjectTable({
2303
2391
  } : {},
2304
2392
  children: /* @__PURE__ */ jsx(ResultField, { field: element, value: item, hideLabel: true })
2305
2393
  }
2306
- ) }) : columns.map((column) => {
2394
+ ) }) : shown.map((column) => {
2307
2395
  const cell = typeof item === "object" && item !== null ? ownProp(item, column.name) : void 0;
2308
2396
  return /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 align-top", children: /* @__PURE__ */ jsx(
2309
2397
  "div",
2310
2398
  {
2311
2399
  className: cn(
2312
2400
  "max-w-[44ch]",
2313
- column.kind === "list" && isInlineColumn(column) ? "min-w-[16ch]" : "truncate"
2401
+ column === name ? "max-w-none min-w-[16ch] wrap-anywhere [&>span]:whitespace-normal" : column.kind === "list" && isInlineColumn(column) ? "min-w-[16ch]" : "truncate"
2314
2402
  ),
2315
- ...typeof cell === "string" || typeof cell === "number" ? { title: String(cell) } : {},
2403
+ ...typeof cell === "string" || typeof cell === "number" ? { title: cellTitle(column, cell, presentation) } : {},
2316
2404
  children: /* @__PURE__ */ jsx(LeafValue, { field: column, value: cell, compact: true })
2317
2405
  }
2318
2406
  ) }, column.name);
@@ -2379,10 +2467,31 @@ function ImageGallery({ items }) {
2379
2467
  function FileRows({ items, kind }) {
2380
2468
  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)) });
2381
2469
  }
2382
- function tableColumns(item) {
2470
+ function recordColumns(item) {
2383
2471
  if (item.kind !== "object" || item.fields.length === 0) return void 0;
2384
2472
  return item.fields;
2385
2473
  }
2474
+ function recordNameField(fields) {
2475
+ return fields.find((member) => member.kind === "text");
2476
+ }
2477
+ function columnTier(field, name) {
2478
+ if (field === name) return 1;
2479
+ if (field.kind !== "list" && fitsACellWhole(field)) return 2;
2480
+ if (field.kind === "text" || field.kind === "list" && isInlineColumn(field)) return 3;
2481
+ return 4;
2482
+ }
2483
+ function chooseColumns(fields, budget) {
2484
+ const name = recordNameField(fields);
2485
+ if (fields.length <= budget) return { shown: fields, hidden: 0, ...name ? { name } : {} };
2486
+ const kept = new Set(
2487
+ fields.map((field, index) => ({ field, index, tier: columnTier(field, name) })).sort((a, b) => a.tier - b.tier || a.index - b.index).slice(0, budget).map(({ field }) => field)
2488
+ );
2489
+ return {
2490
+ shown: fields.filter((field) => kept.has(field)),
2491
+ hidden: fields.length - kept.size,
2492
+ ...name ? { name } : {}
2493
+ };
2494
+ }
2386
2495
  function ResultField({ field, value, depth = 0, hideLabel = false }) {
2387
2496
  const s = useFieldStrings();
2388
2497
  const unwrapped = unwrap(field, value);
@@ -2454,13 +2563,21 @@ function ResultField({ field, value, depth = 0, hideLabel = false }) {
2454
2563
  }
2455
2564
  case "list": {
2456
2565
  const items = Array.isArray(unwrapped) ? unwrapped : [];
2457
- const columns = tableColumns(field.item);
2566
+ const columns = recordColumns(field.item);
2458
2567
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2459
2568
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
2460
2569
  header,
2461
2570
  /* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: s.itemsCount(items.length) })
2462
2571
  ] }),
2463
- items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-[13px] italic text-muted-foreground", children: s.noItemsYet }) : columns ? (
2572
+ items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-[13px] italic text-muted-foreground", children: s.noItemsYet }) : isNativeValueNode(field.item) ? (
2573
+ // A list of dates, pages or composites is a list of VALUES, each read
2574
+ // by its own arm - one per line. Their nodes are `object` (or, for a
2575
+ // composite, `unknown`), so the branches below would tabulate a
2576
+ // date's `date` and `time`, print a page's markup source down a
2577
+ // column named by it, and show a composite as JSON. The arm keyed by
2578
+ // concept is the one that knows what each entry is.
2579
+ /* @__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(ResultField, { field: field.item, value: item, depth: depth + 1, hideLabel: true }) }, index)) })
2580
+ ) : columns ? (
2464
2581
  // Same shape every row: the labels are column headers, not per-row
2465
2582
  // labels. See `ObjectTable`.
2466
2583
  /* @__PURE__ */ jsx(
@@ -2616,6 +2733,11 @@ function highlight(json) {
2616
2733
  function JsonView({ value }) {
2617
2734
  const s = useFieldStrings();
2618
2735
  const [copied, setCopied] = useState(false);
2736
+ useEffect(() => {
2737
+ if (!copied) return;
2738
+ const timer = window.setTimeout(() => setCopied(false), 1500);
2739
+ return () => window.clearTimeout(timer);
2740
+ }, [copied]);
2619
2741
  const text = value === null || value === void 0 ? void 0 : typeof value === "object" ? stringifyValue(value) : JSON.stringify(value);
2620
2742
  if (text === void 0) {
2621
2743
  return /* @__PURE__ */ jsx(Absent, {});
@@ -2626,10 +2748,7 @@ function JsonView({ value }) {
2626
2748
  {
2627
2749
  type: "button",
2628
2750
  onClick: () => {
2629
- void navigator.clipboard.writeText(text).then(() => {
2630
- setCopied(true);
2631
- window.setTimeout(() => setCopied(false), 1500);
2632
- });
2751
+ void navigator.clipboard.writeText(text).then(() => setCopied(true));
2633
2752
  },
2634
2753
  "aria-label": s.copyJson,
2635
2754
  className: "absolute right-2 top-2 rounded border border-border bg-card p-1 text-muted-foreground hover:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
@@ -2717,5 +2836,5 @@ function StuffViewer({
2717
2836
  }
2718
2837
 
2719
2838
  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, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, cn, downloadStuff, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
2720
- //# sourceMappingURL=chunk-KG4IYFWI.js.map
2721
- //# sourceMappingURL=chunk-KG4IYFWI.js.map
2839
+ //# sourceMappingURL=chunk-G6BWZUGV.js.map
2840
+ //# sourceMappingURL=chunk-G6BWZUGV.js.map