@pipelex/mthds-form 0.25.0 → 0.26.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.
@@ -3,7 +3,7 @@ import { seedInputs, closeOptionalStructures, openOptionalStructures } from './c
3
3
  import { sourceFileValue, roomForChosenFiles, roomForDroppedFiles, addChosenFiles, placeDroppedFiles, chosenFilesAdded } from './chunk-NSHGLM6A.js';
4
4
  import { readStuffFile } from './chunk-QINLJ5RM.js';
5
5
  import { apiInputsFromRunValues, sameRunValues, runValuesFromInputs, setValueAtPath } from './chunk-ZX52CQJ4.js';
6
- import { TYPED_SCALAR_MARKERS, viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isNativePageNode, nativeVerdictOf, isNativeValueNode, readDateContent, formatDateContent, readPageContent, readYesNoContent, readRatingContent, readChoiceContent, readImageContent, readDataUrl, readDocumentContent } from './chunk-JSPF4BQP.js';
6
+ import { TYPED_SCALAR_MARKERS, viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isNativePageNode, nativeVerdictOf, isNativeValueNode, readDateContent, formatDateContent, readPageContent, readYesNoContent, readRatingContent, readChoiceContent, readDocumentContent, readImageContent, readDataUrl } from './chunk-JSPF4BQP.js';
7
7
  import { buildRunFields } from './chunk-ZB4RYUKV.js';
8
8
  import { conceptCategory } from './chunk-FABY3DEY.js';
9
9
  import { acceptLabel, acceptMap, isAcceptedFile, normalizeMimeType, formatOfFilename, DOCUMENT_FORMATS, IMAGE_FORMATS } from './chunk-XCO6GTEC.js';
@@ -12,10 +12,10 @@ import { ownProp } from './chunk-V3OV2XQT.js';
12
12
  import { toStoredDateValue, toDateInputValue } from './chunk-GKV77RAS.js';
13
13
  import { clsx } from 'clsx';
14
14
  import { twMerge } from 'tailwind-merge';
15
- import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image as Image$1, FileText, CalendarDays, Type, Folder, Search, X, ImageIcon, Loader2, ImageOff, Upload, Link2, EyeOff, Eye, Minus, Plus, Trash2, Sparkles, MoreHorizontal, TriangleAlert, FileDown, Copy, Download, ChevronRight } from 'lucide-react';
15
+ import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image as Image$1, FileText, CalendarDays, Type, Folder, Search, X, ImageIcon, Loader2, ImageOff, Upload, Link2, EyeOff, Eye, Minus, Plus, Trash2, Sparkles, MoreHorizontal, TriangleAlert, FileDown, Copy, Download, ChevronRight, ExternalLink, ChevronLeft } from 'lucide-react';
16
16
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
17
  import * as React5 from 'react';
18
- import { createContext, use, useId, useMemo, useContext, useState, useEffect, useRef, useLayoutEffect, useCallback, Fragment as Fragment$1 } from 'react';
18
+ import { createContext, use, useId, useMemo, useContext, useState, useEffect, useRef, useLayoutEffect, useCallback, Fragment as Fragment$1, useSyncExternalStore } from 'react';
19
19
  import * as SwitchPrimitives from '@radix-ui/react-switch';
20
20
  import { useDropzone } from 'react-dropzone';
21
21
  import * as DialogPrimitive from '@radix-ui/react-dialog';
@@ -25,6 +25,7 @@ import { cva } from 'class-variance-authority';
25
25
  import * as SelectPrimitive from '@radix-ui/react-select';
26
26
  import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
27
27
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
28
+ import { createPortal } from 'react-dom';
28
29
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
29
30
  import { marked } from 'marked';
30
31
 
@@ -255,6 +256,8 @@ var EN_FIELD_STRINGS = {
255
256
  preview: "Preview",
256
257
  removeFileAria: "Remove file",
257
258
  previewUnavailablePdf: "Preview unavailable - open to view.",
259
+ pdfViewerUnavailable: "This browser cannot show the PDF here.",
260
+ openInNewTab: "Open in a new tab",
258
261
  // U+002D HYPHEN-MINUS, deliberately: not an en dash, not an em dash.
259
262
  resultAbsent: "-",
260
263
  resultAbsentDescription: "Not provided",
@@ -287,6 +290,11 @@ var EN_FIELD_STRINGS = {
287
290
  ratingLevel: (position) => `Level ${position}`,
288
291
  pageNumber: (position) => `Page ${position}`,
289
292
  extractedTable: "Table",
293
+ viewImage: (name) => `View ${name}`,
294
+ imagePosition: (position, count) => `${position} of ${count}`,
295
+ previousImage: "Previous picture",
296
+ nextImage: "Next picture",
297
+ openOriginal: "Open the original",
290
298
  unsupportedFileType: (formats) => `That file type is not supported. Accepted formats: ${formats}.`,
291
299
  dropFilesOrBrowse: "Drop files or click to browse",
292
300
  dropFilesToAdd: "Drop to add the files",
@@ -431,6 +439,8 @@ var FR_FIELD_STRINGS = {
431
439
  preview: "Aper\xE7u",
432
440
  removeFileAria: "Retirer le fichier",
433
441
  previewUnavailablePdf: "Aper\xE7u indisponible - ouvrez le fichier pour le consulter.",
442
+ pdfViewerUnavailable: "Ce navigateur ne peut pas afficher le PDF ici.",
443
+ openInNewTab: "Ouvrir dans un nouvel onglet",
434
444
  // U+002D HYPHEN-MINUS, as in the English defaults.
435
445
  resultAbsent: "-",
436
446
  resultAbsentDescription: "Non renseign\xE9",
@@ -463,6 +473,11 @@ var FR_FIELD_STRINGS = {
463
473
  ratingLevel: (position) => `Niveau ${position}`,
464
474
  pageNumber: (position) => `Page ${position}`,
465
475
  extractedTable: "Tableau",
476
+ viewImage: (name) => `Voir ${name}`,
477
+ imagePosition: (position, count) => `${position} sur ${count}`,
478
+ previousImage: "Image pr\xE9c\xE9dente",
479
+ nextImage: "Image suivante",
480
+ openOriginal: "Ouvrir l\u2019original",
466
481
  unsupportedFileType: (formats) => `Ce type de fichier n\u2019est pas pris en charge. Formats accept\xE9s\xA0: ${formats}.`,
467
482
  dropFilesOrBrowse: "D\xE9posez des fichiers ou cliquez pour parcourir",
468
483
  dropFilesToAdd: "D\xE9posez pour ajouter les fichiers",
@@ -841,6 +856,10 @@ function closeAutoFocus(onCloseAutoFocus, returnFocus) {
841
856
  target.focus();
842
857
  };
843
858
  }
859
+ var keepPress = (event) => event.stopPropagation();
860
+ function DialogSurface({ children }) {
861
+ return /* @__PURE__ */ jsx("div", { className: "contents", onClick: keepPress, onMouseDown: keepPress, children });
862
+ }
844
863
  var DialogOverlay = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
845
864
  DialogPrimitive.Overlay,
846
865
  {
@@ -850,7 +869,7 @@ var DialogOverlay = React5.forwardRef(({ className, ...props }, ref) => /* @__PU
850
869
  }
851
870
  ));
852
871
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
853
- var DialogContent = React5.forwardRef(({ className, layout = "dialog", returnFocus, onCloseAutoFocus, children, ...props }, ref) => /* @__PURE__ */ jsxs(Fragment, { children: [
872
+ var DialogContent = React5.forwardRef(({ className, layout = "dialog", returnFocus, onCloseAutoFocus, children, ...props }, ref) => /* @__PURE__ */ jsxs(DialogSurface, { children: [
854
873
  /* @__PURE__ */ jsx(DialogOverlay, {}),
855
874
  /* @__PURE__ */ jsx(
856
875
  DialogPrimitive.Content,
@@ -1005,7 +1024,6 @@ function FilePicker({
1005
1024
  onCloseAutoFocus,
1006
1025
  returnFocus: () => returnFocusTo?.current ?? heldFocus.current,
1007
1026
  style: drag > 0 ? { transform: `translateY(${drag}px)` } : void 0,
1008
- onClick: (event) => event.stopPropagation(),
1009
1027
  children: /* @__PURE__ */ jsx(FilePickerBody, { ...props, onDrag: setDrag, onClose: () => onOpenChange(false) })
1010
1028
  }
1011
1029
  ) });
@@ -1046,7 +1064,7 @@ function FilePickerBody({
1046
1064
  return next;
1047
1065
  });
1048
1066
  const choices = [...chosen.values()];
1049
- const use7 = () => {
1067
+ const use8 = () => {
1050
1068
  if (choices.length === 0) return;
1051
1069
  onUse(choices);
1052
1070
  onClose();
@@ -1151,7 +1169,7 @@ function FilePickerBody({
1151
1169
  "button",
1152
1170
  {
1153
1171
  type: "button",
1154
- onClick: use7,
1172
+ onClick: use8,
1155
1173
  disabled: choices.length === 0,
1156
1174
  className: cn(
1157
1175
  "flex h-12 w-full items-center justify-center rounded-md bg-primary px-4 text-[16px] font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50",
@@ -2909,7 +2927,7 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
2909
2927
  }
2910
2928
  }
2911
2929
  var AlertDialog = AlertDialogPrimitive.Root;
2912
- var AlertDialogContent = React5.forwardRef(({ className, returnFocus, onCloseAutoFocus, ...props }, ref) => /* @__PURE__ */ jsxs(Fragment, { children: [
2930
+ var AlertDialogContent = React5.forwardRef(({ className, returnFocus, onCloseAutoFocus, ...props }, ref) => /* @__PURE__ */ jsxs(DialogSurface, { children: [
2913
2931
  /* @__PURE__ */ jsx(AlertDialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-black/50" }),
2914
2932
  /* @__PURE__ */ jsx(
2915
2933
  AlertDialogPrimitive.Content,
@@ -3091,7 +3109,6 @@ function FillWithExample({
3091
3109
  className: "top-auto max-h-[calc(100dvh-1.5rem)]",
3092
3110
  ref: sheet,
3093
3111
  returnFocus: () => control.current ?? afterControl.current,
3094
- onClick: (event) => event.stopPropagation(),
3095
3112
  children: [
3096
3113
  /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3097
3114
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1 pt-1", children: [
@@ -3176,7 +3193,6 @@ function FillWithExample({
3176
3193
  const row = rows.current.get(replacing.id);
3177
3194
  return row?.isConnected ? row : control.current;
3178
3195
  },
3179
- onClick: (event) => event.stopPropagation(),
3180
3196
  children: [
3181
3197
  /* @__PURE__ */ jsx(AlertDialogTitle, { children: s.replaceEnteredTitle }),
3182
3198
  /* @__PURE__ */ jsx(AlertDialogDescription, { children: s.replaceEnteredDescription(replacing.name) }),
@@ -3267,7 +3283,7 @@ function RenameExampleDialog({
3267
3283
  onOpenChange: (next) => {
3268
3284
  if (!next && state !== "busy") onCancel();
3269
3285
  },
3270
- children: /* @__PURE__ */ jsxs(DialogContent, { returnFocus, onClick: (event) => event.stopPropagation(), children: [
3286
+ children: /* @__PURE__ */ jsxs(DialogContent, { returnFocus, children: [
3271
3287
  /* @__PURE__ */ jsx(DialogTitle, { children: s.renameExampleTitle }),
3272
3288
  /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: example.name }),
3273
3289
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
@@ -3346,7 +3362,7 @@ function DeleteExampleDialog({
3346
3362
  onOpenChange: (next) => {
3347
3363
  if (!next && state !== "busy") onCancel();
3348
3364
  },
3349
- children: /* @__PURE__ */ jsxs(AlertDialogContent, { returnFocus, onClick: (event) => event.stopPropagation(), children: [
3365
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { returnFocus, children: [
3350
3366
  /* @__PURE__ */ jsx(AlertDialogTitle, { children: s.deleteExampleTitle(example.name) }),
3351
3367
  /* @__PURE__ */ jsx(AlertDialogDescription, { children: s.deleteExampleDescription }),
3352
3368
  state === "failed" && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[13px] text-destructive", children: s.deleteFailed }),
@@ -4098,6 +4114,7 @@ var ResultEnvContext = createContext({});
4098
4114
  function ResultEnvProvider({
4099
4115
  resolveUrl,
4100
4116
  resolveUrls,
4117
+ resolveRendition,
4101
4118
  resolveShareUrl,
4102
4119
  proseImages,
4103
4120
  tableColumns,
@@ -4111,6 +4128,7 @@ function ResultEnvProvider({
4111
4128
  () => ({
4112
4129
  resolveUrl,
4113
4130
  resolveUrls,
4131
+ resolveRendition,
4114
4132
  resolveShareUrl,
4115
4133
  proseImages,
4116
4134
  tableColumns,
@@ -4122,6 +4140,7 @@ function ResultEnvProvider({
4122
4140
  [
4123
4141
  resolveUrl,
4124
4142
  resolveUrls,
4143
+ resolveRendition,
4125
4144
  resolveShareUrl,
4126
4145
  proseImages,
4127
4146
  tableColumns,
@@ -4171,6 +4190,9 @@ function useMarkupResolvers() {
4171
4190
  const { resolveUrl, resolveUrls } = use(ResultEnvContext);
4172
4191
  return useMemo(() => ({ resolveUrl, resolveUrls }), [resolveUrl, resolveUrls]);
4173
4192
  }
4193
+ function useResolveRendition() {
4194
+ return use(ResultEnvContext).resolveRendition;
4195
+ }
4174
4196
  function useResolveShareUrl() {
4175
4197
  return use(ResultEnvContext).resolveShareUrl;
4176
4198
  }
@@ -4226,6 +4248,34 @@ function useTableColumns() {
4226
4248
  function useTableLayout() {
4227
4249
  return use(ResultEnvContext).tableLayout ?? "scroll";
4228
4250
  }
4251
+ var OverlayLayerContext = createContext(null);
4252
+ function OverlayLayer({ children }) {
4253
+ const outer = use(OverlayLayerContext);
4254
+ const [holders, setHolders] = useState(0);
4255
+ const [target, setTarget] = useState(null);
4256
+ const acquire = useCallback(() => {
4257
+ setHolders((count) => count + 1);
4258
+ let released = false;
4259
+ return () => {
4260
+ if (released) return;
4261
+ released = true;
4262
+ setHolders((count) => count - 1);
4263
+ };
4264
+ }, []);
4265
+ const layer = useMemo(() => ({ target, acquire }), [target, acquire]);
4266
+ if (outer) return children;
4267
+ return /* @__PURE__ */ jsxs(OverlayLayerContext, { value: layer, children: [
4268
+ holders > 0 && /* @__PURE__ */ jsx("span", { ref: setTarget, "data-overlay-layer": true, className: "absolute size-0" }),
4269
+ children
4270
+ ] });
4271
+ }
4272
+ function InOverlayLayer({ children }) {
4273
+ const layer = use(OverlayLayerContext);
4274
+ const acquire = layer?.acquire;
4275
+ useEffect(() => acquire?.(), [acquire]);
4276
+ if (!layer) return children;
4277
+ return layer.target ? createPortal(children, layer.target) : null;
4278
+ }
4229
4279
  var ResultLocationContext = createContext(void 0);
4230
4280
  function useResultLocation() {
4231
4281
  return use(ResultLocationContext);
@@ -4237,7 +4287,7 @@ function ResultRoot({
4237
4287
  }) {
4238
4288
  const key = path.join("\0");
4239
4289
  const location = useMemo(() => ({ baseName, path: key.split("\0") }), [baseName, key]);
4240
- return /* @__PURE__ */ jsx(ResultLocationContext, { value: location, children });
4290
+ return /* @__PURE__ */ jsx(ResultLocationContext, { value: location, children: /* @__PURE__ */ jsx(OverlayLayer, { children }) });
4241
4291
  }
4242
4292
  function ResultAt({ segment, children }) {
4243
4293
  const parent = use(ResultLocationContext);
@@ -4324,6 +4374,17 @@ function useFrameFocus(frame) {
4324
4374
  }, [frame]);
4325
4375
  return focused;
4326
4376
  }
4377
+ var subscribe = () => () => {
4378
+ };
4379
+ function browserViewer() {
4380
+ return navigator.pdfViewerEnabled === false ? "no" : "yes";
4381
+ }
4382
+ function serverViewer() {
4383
+ return "unknown";
4384
+ }
4385
+ function usePdfViewer() {
4386
+ return useSyncExternalStore(subscribe, browserViewer, serverViewer);
4387
+ }
4327
4388
  var TooltipProvider = TooltipPrimitive.Provider;
4328
4389
  var TooltipRoot = TooltipPrimitive.Root;
4329
4390
  var TooltipTrigger = TooltipPrimitive.Trigger;
@@ -5317,6 +5378,231 @@ function useResolvedMarkup(markup) {
5317
5378
  if (!needed) return markup;
5318
5379
  return shown?.source === markup ? shown.html : void 0;
5319
5380
  }
5381
+ var SWIPE_DISTANCE = 40;
5382
+ function ImageViewer({
5383
+ pictures,
5384
+ index,
5385
+ onIndexChange,
5386
+ onClose,
5387
+ returnFocus
5388
+ }) {
5389
+ const s = useFieldStrings();
5390
+ const panel = useRef(null);
5391
+ const swipe = useRef(null);
5392
+ const count = pictures.length;
5393
+ const at = Math.min(Math.max(index, 0), count - 1);
5394
+ const picture = pictures[at];
5395
+ if (!picture) return null;
5396
+ const several = count > 1;
5397
+ const go = (step) => {
5398
+ const next = at + step;
5399
+ if (next >= 0 && next < count) onIndexChange(next);
5400
+ };
5401
+ const onPointerDown = (event) => {
5402
+ swipe.current = event.pointerType === "touch" && event.isPrimary ? { id: event.pointerId, x: event.clientX, y: event.clientY } : null;
5403
+ };
5404
+ const onPointerUp = (event) => {
5405
+ const start = swipe.current;
5406
+ swipe.current = null;
5407
+ if (!start || start.id !== event.pointerId) return;
5408
+ const across = event.clientX - start.x;
5409
+ const down = event.clientY - start.y;
5410
+ if (Math.abs(across) < SWIPE_DISTANCE || Math.abs(across) < Math.abs(down) * 1.5) return;
5411
+ go(across < 0 ? 1 : -1);
5412
+ };
5413
+ return /* @__PURE__ */ jsx(InOverlayLayer, { children: /* @__PURE__ */ jsx(
5414
+ Dialog,
5415
+ {
5416
+ open: true,
5417
+ onOpenChange: (open) => {
5418
+ if (!open) onClose();
5419
+ },
5420
+ children: /* @__PURE__ */ jsxs(
5421
+ DialogContent,
5422
+ {
5423
+ ref: panel,
5424
+ returnFocus,
5425
+ onOpenAutoFocus: (event) => {
5426
+ event.preventDefault();
5427
+ panel.current?.focus();
5428
+ },
5429
+ onEscapeKeyDown: (event) => {
5430
+ if (event.target instanceof Element && event.target.closest("[data-file-name-backdrop]")) {
5431
+ event.preventDefault();
5432
+ }
5433
+ },
5434
+ onKeyDown: (event) => {
5435
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5436
+ return;
5437
+ }
5438
+ if (isTextEntry(event.target)) return;
5439
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
5440
+ event.preventDefault();
5441
+ event.stopPropagation();
5442
+ go(event.key === "ArrowLeft" ? -1 : 1);
5443
+ },
5444
+ ...picture.caption ? {} : { "aria-describedby": void 0 },
5445
+ className: cn(
5446
+ "m-0 h-dvh w-full max-w-none gap-0 rounded-none border-0 p-0",
5447
+ "sm:m-auto sm:h-[calc(100dvh-4rem)] sm:w-[calc(100%-4rem)] sm:max-w-6xl sm:rounded-xl sm:border"
5448
+ ),
5449
+ children: [
5450
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-border py-1.5 pr-1.5 pl-4", children: [
5451
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
5452
+ /* @__PURE__ */ jsx(DialogTitle, { className: "truncate text-[14px] font-medium", children: picture.name }),
5453
+ several && // A live region from the moment the viewer opens, so each move
5454
+ // is announced: one inserted with its text already in it is not.
5455
+ /* @__PURE__ */ jsx(
5456
+ "p",
5457
+ {
5458
+ "aria-live": "polite",
5459
+ "aria-atomic": "true",
5460
+ className: "text-[12px] text-muted-foreground",
5461
+ children: s.imagePosition(at + 1, count)
5462
+ }
5463
+ )
5464
+ ] }),
5465
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
5466
+ /* @__PURE__ */ jsxs(
5467
+ "a",
5468
+ {
5469
+ href: picture.src,
5470
+ target: "_blank",
5471
+ rel: "noreferrer",
5472
+ className: cn(
5473
+ "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[12px] text-muted-foreground hover:bg-muted hover:text-foreground max-sm:size-11 max-sm:justify-center max-sm:p-0",
5474
+ focusOutlineClass
5475
+ ),
5476
+ children: [
5477
+ /* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": true, className: "size-3.5" }),
5478
+ /* @__PURE__ */ jsx("span", { className: "max-sm:sr-only", children: s.openOriginal })
5479
+ ]
5480
+ }
5481
+ ),
5482
+ picture.actions,
5483
+ /* @__PURE__ */ jsx(
5484
+ DialogClose,
5485
+ {
5486
+ "aria-label": s.close,
5487
+ className: cn(
5488
+ "flex size-9 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground max-sm:size-11",
5489
+ focusOutlineClass
5490
+ ),
5491
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-4" })
5492
+ }
5493
+ )
5494
+ ] })
5495
+ ] }),
5496
+ /* @__PURE__ */ jsxs(
5497
+ "div",
5498
+ {
5499
+ "data-viewer-stage": true,
5500
+ onPointerDown,
5501
+ onPointerUp,
5502
+ onPointerCancel: () => {
5503
+ swipe.current = null;
5504
+ },
5505
+ className: "relative min-h-0 flex-1 touch-pan-y touch-pinch-zoom",
5506
+ children: [
5507
+ /* @__PURE__ */ jsx(
5508
+ ViewerImage,
5509
+ {
5510
+ src: picture.src,
5511
+ alt: picture.caption ?? picture.name
5512
+ },
5513
+ picture.src
5514
+ ),
5515
+ several && /* @__PURE__ */ jsxs(Fragment, { children: [
5516
+ /* @__PURE__ */ jsx(
5517
+ StepButton2,
5518
+ {
5519
+ label: s.previousImage,
5520
+ unavailable: at === 0,
5521
+ onStep: () => go(-1),
5522
+ className: "left-2",
5523
+ children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": true, className: "size-5" })
5524
+ }
5525
+ ),
5526
+ /* @__PURE__ */ jsx(
5527
+ StepButton2,
5528
+ {
5529
+ label: s.nextImage,
5530
+ unavailable: at === count - 1,
5531
+ onStep: () => go(1),
5532
+ className: "right-2",
5533
+ children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true, className: "size-5" })
5534
+ }
5535
+ )
5536
+ ] })
5537
+ ]
5538
+ }
5539
+ ),
5540
+ picture.caption && /* @__PURE__ */ jsx(DialogDescription, { className: "max-h-24 shrink-0 overflow-y-auto border-t border-border px-4 py-2 text-[13px]", children: picture.caption })
5541
+ ]
5542
+ }
5543
+ )
5544
+ }
5545
+ ) });
5546
+ }
5547
+ function ViewerImage({ src, alt }) {
5548
+ const [loading, setLoading] = useState(true);
5549
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5550
+ loading && /* @__PURE__ */ jsx(
5551
+ "span",
5552
+ {
5553
+ role: "status",
5554
+ "aria-label": alt,
5555
+ className: "absolute inset-0 flex items-center justify-center",
5556
+ children: /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-5 animate-spin text-muted-foreground" })
5557
+ }
5558
+ ),
5559
+ /* @__PURE__ */ jsx(
5560
+ "img",
5561
+ {
5562
+ src,
5563
+ alt,
5564
+ referrerPolicy: "no-referrer",
5565
+ decoding: "async",
5566
+ draggable: false,
5567
+ onLoad: () => setLoading(false),
5568
+ onError: () => setLoading(false),
5569
+ className: cn(
5570
+ "absolute inset-0 size-full object-contain p-2 sm:p-4",
5571
+ loading && "opacity-0"
5572
+ )
5573
+ }
5574
+ )
5575
+ ] });
5576
+ }
5577
+ function StepButton2({
5578
+ label,
5579
+ unavailable,
5580
+ onStep,
5581
+ className,
5582
+ children
5583
+ }) {
5584
+ return /* @__PURE__ */ jsx(
5585
+ "button",
5586
+ {
5587
+ type: "button",
5588
+ "aria-label": label,
5589
+ "aria-disabled": unavailable,
5590
+ onClick: () => {
5591
+ if (!unavailable) onStep();
5592
+ },
5593
+ className: cn(
5594
+ "absolute top-1/2 flex size-11 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-popover/90 text-foreground shadow-sm hover:bg-muted aria-disabled:opacity-40 aria-disabled:hover:bg-popover/90",
5595
+ focusOutlineClass,
5596
+ className
5597
+ ),
5598
+ children
5599
+ }
5600
+ );
5601
+ }
5602
+ function isTextEntry(target) {
5603
+ if (!(target instanceof HTMLElement)) return false;
5604
+ return target.isContentEditable || target.closest("input, textarea, select") !== null;
5605
+ }
5320
5606
  var LEX_OPTIONS = { gfm: true, breaks: true };
5321
5607
  function Markdown({ text, className, proseImages }) {
5322
5608
  const tokens = React5.useMemo(() => marked.lexer(text, LEX_OPTIONS), [text]);
@@ -6359,6 +6645,7 @@ function pathLabel(url) {
6359
6645
  return segment && segment.length > 0 ? segment : url;
6360
6646
  }
6361
6647
  var FILE_BUTTON_ON_A_PHONE = "max-sm:grid max-sm:size-11 max-sm:place-items-center";
6648
+ var CARD_ACTION = "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-2 py-1 text-[12px] hover:bg-card max-sm:min-h-11 max-sm:px-3";
6362
6649
  function CopyButton({
6363
6650
  value,
6364
6651
  provide,
@@ -6473,7 +6760,7 @@ function useStuffFile(kind, value) {
6473
6760
  const location = useResultLocation();
6474
6761
  return location ? readStuffFile(kind, value, location.path.join(".")) : void 0;
6475
6762
  }
6476
- function FileDownloadButton({ file }) {
6763
+ function FileDownloadButton({ file, labelled = false }) {
6477
6764
  const s = useFieldStrings();
6478
6765
  const location = useResultLocation();
6479
6766
  const resolve = useResolveUrl();
@@ -6502,7 +6789,7 @@ ${planned.url ?? planned.text}`;
6502
6789
  const failed = state === "failed";
6503
6790
  const label = s.downloadFile(planned.name);
6504
6791
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6505
- /* @__PURE__ */ jsx(
6792
+ /* @__PURE__ */ jsxs(
6506
6793
  "button",
6507
6794
  {
6508
6795
  type: "button",
@@ -6521,12 +6808,17 @@ ${planned.url ?? planned.text}`;
6521
6808
  title: failed ? s.downloadFileFailed : label,
6522
6809
  ...failed ? { "aria-describedby": statusId } : {},
6523
6810
  className: cn(
6524
- "shrink-0 rounded p-0.5 hover:bg-card disabled:opacity-60",
6811
+ labelled ? cn(CARD_ACTION, "disabled:opacity-60") : cn(
6812
+ "shrink-0 rounded p-0.5 hover:bg-card disabled:opacity-60",
6813
+ FILE_BUTTON_ON_A_PHONE
6814
+ ),
6525
6815
  focusOutlineClass,
6526
- FILE_BUTTON_ON_A_PHONE,
6527
6816
  failed ? "text-destructive" : "text-muted-foreground hover:text-foreground"
6528
6817
  ),
6529
- children: state === "saving" ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-3.5 animate-spin" }) : state === "saved" ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : failed ? /* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Download, { "aria-hidden": true, className: "size-3.5" })
6818
+ children: [
6819
+ state === "saving" ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-3.5 animate-spin" }) : state === "saved" ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : failed ? /* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Download, { "aria-hidden": true, className: "size-3.5" }),
6820
+ labelled && s.download
6821
+ ]
6530
6822
  }
6531
6823
  ),
6532
6824
  /* @__PURE__ */ jsx("span", { id: statusId, role: "status", className: "sr-only", children: failed ? s.downloadFileFailed : "" }),
@@ -6556,7 +6848,32 @@ function previewableUrl(content, resolve) {
6556
6848
  const renderable = content.mimeType ? PREVIEWABLE_MIME_RE.test(content.mimeType) : PREVIEWABLE_EXT_RE.test(named);
6557
6849
  return renderable ? url : void 0;
6558
6850
  }
6559
- function DocumentPreview({ url, name }) {
6851
+ function isPdfDocument(content, url) {
6852
+ return content.mimeType ? /^application\/pdf\s*(;|$)/i.test(content.mimeType) : /\.pdf(\?|#|$)/i.test(content.filename ?? url);
6853
+ }
6854
+ function DocumentPreview({
6855
+ url,
6856
+ name,
6857
+ pdf,
6858
+ file,
6859
+ whole
6860
+ }) {
6861
+ const viewer = usePdfViewer();
6862
+ const size = whole ? WHOLE_RESULT_FRAME : NESTED_FRAME;
6863
+ if (pdf && viewer === "no") return /* @__PURE__ */ jsx(PdfUnavailable, { url, name, file });
6864
+ if (pdf && viewer === "unknown") {
6865
+ return /* @__PURE__ */ jsx(
6866
+ "div",
6867
+ {
6868
+ "aria-hidden": true,
6869
+ className: cn(
6870
+ size,
6871
+ "flex items-center justify-center rounded-lg border border-border bg-card/40"
6872
+ ),
6873
+ children: /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" })
6874
+ }
6875
+ );
6876
+ }
6560
6877
  return /* @__PURE__ */ jsx(
6561
6878
  FocusFrame,
6562
6879
  {
@@ -6564,19 +6881,56 @@ function DocumentPreview({ url, name }) {
6564
6881
  title: name,
6565
6882
  loading: "lazy",
6566
6883
  referrerPolicy: "no-referrer",
6567
- className: "h-112 w-full rounded-lg border border-border bg-card/40"
6884
+ className: cn(size, "rounded-lg border border-border bg-card/40")
6568
6885
  }
6569
6886
  );
6570
6887
  }
6571
- function DocumentValue({ value }) {
6888
+ var NESTED_FRAME = "h-112 w-full";
6889
+ var WHOLE_RESULT_FRAME = "aspect-[1/1.414] max-h-[80vh] min-h-80 w-full";
6890
+ function PdfUnavailable({
6891
+ url,
6892
+ name,
6893
+ file
6894
+ }) {
6895
+ const s = useFieldStrings();
6896
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card/40 px-3.5 py-3 sm:flex-row sm:items-center", children: [
6897
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-2.5", children: [
6898
+ /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
6899
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-0.5", children: [
6900
+ /* @__PURE__ */ jsx("p", { title: name, className: "truncate text-[13px] font-medium text-foreground", children: name }),
6901
+ /* @__PURE__ */ jsx("p", { className: "text-[12.5px] leading-relaxed text-muted-foreground", children: s.pdfViewerUnavailable })
6902
+ ] })
6903
+ ] }),
6904
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
6905
+ /* @__PURE__ */ jsxs(
6906
+ "a",
6907
+ {
6908
+ href: url,
6909
+ target: "_blank",
6910
+ rel: "noreferrer",
6911
+ className: cn(CARD_ACTION, "text-foreground", focusOutlineClass),
6912
+ children: [
6913
+ /* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": true, className: "size-3.5" }),
6914
+ s.openInNewTab
6915
+ ]
6916
+ }
6917
+ ),
6918
+ file && /* @__PURE__ */ jsx(FileDownloadButton, { file, labelled: true })
6919
+ ] })
6920
+ ] });
6921
+ }
6922
+ function DocumentValue({ value, whole = false }) {
6572
6923
  const s = useFieldStrings();
6573
6924
  const resolve = useResolveUrl();
6925
+ const isApp = useFieldPresentation() === "app";
6574
6926
  const [open, setOpen] = useState(false);
6575
6927
  const file = useStuffFile("document", value);
6576
6928
  const content = readDocumentContent(value);
6577
6929
  if (!content) return /* @__PURE__ */ jsx(Absent, {});
6578
6930
  const name = content.title ?? content.filename ?? fileLabel(content.url, content.filename, s);
6579
6931
  const preview = previewableUrl(content, resolve);
6932
+ const pdf = preview !== void 0 && isPdfDocument(content, preview);
6933
+ const opened = whole && isApp && pdf;
6580
6934
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
6581
6935
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [
6582
6936
  /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
@@ -6594,7 +6948,7 @@ function DocumentValue({ value }) {
6594
6948
  ),
6595
6949
  content.snippet && /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-[12px] leading-relaxed text-muted-foreground", children: content.snippet })
6596
6950
  ] }),
6597
- preview && /* @__PURE__ */ jsxs(
6951
+ preview && !opened && /* @__PURE__ */ jsxs(
6598
6952
  "button",
6599
6953
  {
6600
6954
  type: "button",
@@ -6611,58 +6965,98 @@ function DocumentValue({ value }) {
6611
6965
  }
6612
6966
  )
6613
6967
  ] }),
6614
- preview && open && /* @__PURE__ */ jsx(DocumentPreview, { url: preview, name })
6968
+ preview && (opened || open) && /* @__PURE__ */ jsx(DocumentPreview, { url: preview, name, pdf, file, whole: opened })
6615
6969
  ] });
6616
6970
  }
6971
+ var TILE_RENDITION_SIDE = 576;
6972
+ var CELL_RENDITION_HEIGHT = 80;
6973
+ var CELL_RENDITION_WIDTH = 256;
6974
+ function renditionWidth(place, content) {
6975
+ const { width, height } = content;
6976
+ const ratio = width !== void 0 && height !== void 0 && width > 0 && height > 0 ? width / height : void 0;
6977
+ if (place === "tile") {
6978
+ return ratio === void 0 ? TILE_RENDITION_SIDE : Math.ceil(TILE_RENDITION_SIDE * Math.max(1, ratio));
6979
+ }
6980
+ return ratio === void 0 ? CELL_RENDITION_WIDTH : Math.max(CELL_RENDITION_WIDTH, Math.ceil(CELL_RENDITION_HEIGHT * ratio));
6981
+ }
6982
+ function renditionUrl(content, whole, resolveRendition, width) {
6983
+ return viewableUrl(resolveRendition?.(content.url, { width })) ?? whole;
6984
+ }
6617
6985
  function ImageValue({
6618
6986
  value,
6619
6987
  inGallery = false,
6620
6988
  inRow = false,
6621
6989
  compact = false,
6622
- downloadable = true
6990
+ downloadable = true,
6991
+ opener
6623
6992
  }) {
6624
6993
  const s = useFieldStrings();
6994
+ const presentation = useFieldPresentation();
6625
6995
  const resolve = useResolveUrl();
6996
+ const resolveRendition = useResolveRendition();
6626
6997
  const stuffFile = useStuffFile("image", value);
6627
6998
  const file = downloadable ? stuffFile : void 0;
6999
+ const [viewing, setViewing] = useState(false);
7000
+ const ownButton = useRef(null);
7001
+ const frame = useRef(null);
6628
7002
  const content = readImageContent(value);
7003
+ const src = content ? paintableUrl(content, resolve) : void 0;
7004
+ if (viewing && src === void 0) setViewing(false);
6629
7005
  if (!content) return /* @__PURE__ */ jsx(Absent, {});
6630
- const src = paintableUrl(content, resolve);
6631
- return /* @__PURE__ */ jsxs("div", { className: inGallery ? "space-y-1" : "space-y-1.5", children: [
6632
- src ? (
6633
- // Wrapped in a link: the picture is a PREVIEW of a file, and clicking a
6634
- // preview to see the thing it previews is what a reader expects. The
6635
- // reference underneath is the other half - a picture with no URL beside
6636
- // it is a result you can look at and cannot use.
7006
+ const place = inGallery ? "tile" : compact ? "cell" : void 0;
7007
+ const drawn = src !== void 0 && place !== void 0 ? renditionUrl(content, src, resolveRendition, renditionWidth(place, content)) : src;
7008
+ const name = src === void 0 ? "" : fileLabel(src, content.filename, s);
7009
+ const fileLine = src !== void 0 && !compact && !(inGallery && presentation === "app");
7010
+ const size = !inGallery && !compact && content.width && content.height ? { width: content.width, height: content.height } : void 0;
7011
+ return /* @__PURE__ */ jsxs("div", { ref: frame, className: inGallery ? "space-y-1" : "space-y-1.5", children: [
7012
+ src !== void 0 && drawn !== void 0 ? (
7013
+ // A button, not a link: the picture opens in the viewer over the
7014
+ // result. Enter and Space open it as a click does.
6637
7015
  /* @__PURE__ */ jsx(
6638
- "a",
7016
+ "button",
6639
7017
  {
6640
- href: src,
6641
- target: "_blank",
6642
- rel: "noreferrer",
6643
- title: content.url,
7018
+ ref: opener ? opener.ref : ownButton,
7019
+ type: "button",
7020
+ onClick: opener ? opener.open : () => setViewing(true),
7021
+ "aria-label": s.viewImage(name),
7022
+ "aria-haspopup": "dialog",
6644
7023
  className: cn(
6645
- inGallery || compact ? "inline-block" : "block w-full",
7024
+ // BLOCK when the picture fills its column. A table cell's
7025
+ // thumbnail sizes itself, and is 44px square at the least below
7026
+ // `sm`, where it is tapped.
7027
+ compact ? "inline-flex items-center justify-center align-top max-sm:min-h-11 max-sm:min-w-11" : "block w-full",
7028
+ "cursor-zoom-in",
6646
7029
  focusOutlineInsetClass
6647
7030
  ),
6648
7031
  children: /* @__PURE__ */ jsx(
6649
7032
  LoadingImage,
6650
7033
  {
6651
- src,
7034
+ src: drawn,
7035
+ ...drawn !== src ? { fallbackSrc: src } : {},
6652
7036
  alt: content.caption ?? s.preview,
7037
+ lazy: inGallery || compact,
7038
+ ...size ? { size } : {},
7039
+ ...inGallery ? (
7040
+ // The tile's box is square before anything arrives, and the
7041
+ // picture covers it once it does: tiles of one size, whatever
7042
+ // the pictures' shapes, and nothing moves when one lands.
7043
+ { box: "aspect-square w-full overflow-hidden" }
7044
+ ) : compact ? { box: "h-10 min-w-10" } : {},
6653
7045
  className: cn(
6654
- inGallery && "block aspect-square w-full object-cover",
6655
- // A cell is one line tall. An image column rendered at the standalone
6656
- // height turns every row into a picture and the table into a
6657
- // slideshow, so a cell gets a thumbnail and the row's expansion gets
6658
- // the picture.
7046
+ inGallery && "absolute inset-0 size-full object-cover",
7047
+ // A cell is one line tall. An image column rendered at the
7048
+ // standalone height turns every row into a picture and the
7049
+ // table into a slideshow, so a cell gets a thumbnail and the
7050
+ // row's expansion gets the picture.
6659
7051
  compact && "h-10 w-auto rounded border border-border object-cover",
6660
7052
  // On its own: the full width it is given, capped generously in
6661
7053
  // height. `max-h-64` was a 256px thumbnail in a panel several
6662
7054
  // times that tall — a generated report image is the RESULT here,
6663
- // not a decoration beside one, and it was unreadable at that size.
6664
- // `object-contain` keeps the aspect ratio when the cap bites.
6665
- !inGallery && !compact && "max-h-176 w-full rounded-lg border border-border object-contain"
7055
+ // not a decoration beside one, and it was unreadable at that
7056
+ // size. `object-contain` keeps the aspect ratio when the cap
7057
+ // bites, and the payload's own width and height, when it states
7058
+ // them, reserve the box before the picture arrives.
7059
+ !inGallery && !compact && "h-auto max-h-176 w-full rounded-lg border border-border object-contain"
6666
7060
  )
6667
7061
  }
6668
7062
  )
@@ -6708,7 +7102,7 @@ function ImageValue({
6708
7102
  file
6709
7103
  }
6710
7104
  ),
6711
- src && !compact && /* @__PURE__ */ jsx("div", { className: inGallery ? "px-2.5" : void 0, children: /* @__PURE__ */ jsx(
7105
+ fileLine && /* @__PURE__ */ jsx("div", { className: inGallery ? "px-2.5" : void 0, children: /* @__PURE__ */ jsx(
6712
7106
  FileRef,
6713
7107
  {
6714
7108
  url: src,
@@ -6727,6 +7121,24 @@ function ImageValue({
6727
7121
  ),
6728
7122
  children: content.caption
6729
7123
  }
7124
+ ),
7125
+ viewing && src !== void 0 && /* @__PURE__ */ jsx(
7126
+ ImageViewer,
7127
+ {
7128
+ pictures: [
7129
+ {
7130
+ src,
7131
+ name,
7132
+ ...content.caption ? { caption: content.caption } : {},
7133
+ actions: /* @__PURE__ */ jsx(FileActions, { copy: { url: src, storageUrl: content.url }, file })
7134
+ }
7135
+ ],
7136
+ index: 0,
7137
+ onIndexChange: () => {
7138
+ },
7139
+ onClose: () => setViewing(false),
7140
+ returnFocus: () => ownButton.current ?? frame.current?.querySelector("a[href], button")
7141
+ }
6730
7142
  )
6731
7143
  ] });
6732
7144
  }
@@ -6776,32 +7188,59 @@ function RecordSummary({ field, value }) {
6776
7188
  }
6777
7189
  return /* @__PURE__ */ jsx(LeafValue, { field: naming, value: ownProp(value, naming.name), compact: true });
6778
7190
  }
6779
- function LoadingImage({ src, alt, className }) {
7191
+ function LoadingImage({
7192
+ src,
7193
+ fallbackSrc,
7194
+ alt,
7195
+ className,
7196
+ box,
7197
+ size,
7198
+ lazy = false
7199
+ }) {
6780
7200
  const [state, setState] = useState("loading");
6781
- const [current, setCurrent] = useState(src);
6782
- if (current !== src) {
6783
- setCurrent(src);
6784
- setState("loading");
7201
+ const [current, setCurrent] = useState({ src, fallbackSrc });
7202
+ const [shown, setShown] = useState(src);
7203
+ if (current.src !== src || current.fallbackSrc !== fallbackSrc) {
7204
+ const next = current.src !== src ? src : shown === current.fallbackSrc ? fallbackSrc ?? src : shown;
7205
+ setCurrent({ src, fallbackSrc });
7206
+ if (next !== shown) {
7207
+ setShown(next);
7208
+ setState("loading");
7209
+ }
6785
7210
  }
6786
- return /* @__PURE__ */ jsxs("span", { className: "relative block", children: [
7211
+ const reserved = box !== void 0 || size !== void 0;
7212
+ return /* @__PURE__ */ jsxs("span", { className: cn("relative block", box), children: [
6787
7213
  state === "loading" && /* @__PURE__ */ jsx(
6788
7214
  "span",
6789
7215
  {
6790
7216
  role: "status",
6791
7217
  "aria-label": alt,
6792
- className: "absolute inset-0 flex items-center justify-center rounded-lg border border-border bg-card/40",
7218
+ className: cn(
7219
+ "absolute inset-0 flex items-center justify-center",
7220
+ // A box the caller drew is the frame; anything else gets one.
7221
+ box ? "bg-muted/40" : "rounded-lg border border-border bg-card/40"
7222
+ ),
6793
7223
  children: /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-4 animate-spin text-muted-foreground" })
6794
7224
  }
6795
7225
  ),
6796
7226
  /* @__PURE__ */ jsx(
6797
7227
  "img",
6798
7228
  {
6799
- src,
7229
+ src: shown,
6800
7230
  alt,
6801
7231
  referrerPolicy: "no-referrer",
7232
+ decoding: "async",
7233
+ ...lazy ? { loading: "lazy" } : {},
7234
+ ...size ? { width: size.width, height: size.height } : {},
6802
7235
  onLoad: () => setState("done"),
6803
- onError: () => setState("done"),
6804
- className: cn(className, state === "loading" && "min-h-40 opacity-0")
7236
+ onError: () => {
7237
+ if (fallbackSrc !== void 0 && shown !== fallbackSrc) setShown(fallbackSrc);
7238
+ else setState("done");
7239
+ },
7240
+ className: cn(
7241
+ className,
7242
+ state === "loading" && (reserved ? "opacity-0" : "min-h-40 opacity-0")
7243
+ )
6805
7244
  }
6806
7245
  )
6807
7246
  ] });
@@ -7264,13 +7703,65 @@ function ScalarLines({ field, items }) {
7264
7703
  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)) });
7265
7704
  }
7266
7705
  function ImageGallery({ items }) {
7706
+ const s = useFieldStrings();
7267
7707
  const resolve = useResolveUrl();
7268
- const anyViewable = items.some((item) => {
7708
+ const [viewing, setViewing] = useState(null);
7709
+ const tiles = useRef(/* @__PURE__ */ new Map());
7710
+ const gallery = useRef(null);
7711
+ const pictures = items.flatMap((item, entry) => {
7269
7712
  const content = readImageContent(item);
7270
- return content ? paintableUrl(content, resolve) !== void 0 : false;
7713
+ const src = content ? paintableUrl(content, resolve) : void 0;
7714
+ return content && src !== void 0 ? [{ entry, item, content, src }] : [];
7271
7715
  });
7272
- if (!anyViewable) return /* @__PURE__ */ jsx(FileRows, { items, kind: "image" });
7273
- 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(ResultAt, { segment: String(index), children: /* @__PURE__ */ jsx(ImageValue, { value: item, inGallery: true }) }) }, index)) });
7716
+ const shown = viewing ? pictures.findIndex((picture) => picture.entry === viewing.entry) : -1;
7717
+ if (viewing && shown === -1) setViewing(null);
7718
+ const returnFocus = (opener) => () => {
7719
+ const first = [...tiles.current.entries()].sort(([a], [b]) => a - b)[0]?.[1];
7720
+ return tiles.current.get(opener) ?? first ?? gallery.current;
7721
+ };
7722
+ const frame = (children) => /* @__PURE__ */ jsx("div", { ref: gallery, tabIndex: -1, className: "outline-none", children });
7723
+ if (pictures.length === 0) return frame(/* @__PURE__ */ jsx(FileRows, { items, kind: "image" }));
7724
+ return frame(
7725
+ /* @__PURE__ */ jsxs(Fragment, { children: [
7726
+ /* @__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(ResultAt, { segment: String(index), children: /* @__PURE__ */ jsx(
7727
+ ImageValue,
7728
+ {
7729
+ value: item,
7730
+ inGallery: true,
7731
+ opener: {
7732
+ open: () => setViewing({ entry: index, opener: index }),
7733
+ ref: (button) => {
7734
+ if (button) tiles.current.set(index, button);
7735
+ else tiles.current.delete(index);
7736
+ }
7737
+ }
7738
+ }
7739
+ ) }) }, index)) }),
7740
+ viewing && shown !== -1 && /* @__PURE__ */ jsx(
7741
+ ImageViewer,
7742
+ {
7743
+ pictures: pictures.map(({ entry, item, content, src }) => ({
7744
+ src,
7745
+ name: fileLabel(src, content.filename, s),
7746
+ ...content.caption ? { caption: content.caption } : {},
7747
+ actions: /* @__PURE__ */ jsx(ResultAt, { segment: String(entry), children: /* @__PURE__ */ jsx(ViewerFileActions, { value: item, src }) })
7748
+ })),
7749
+ index: shown,
7750
+ onIndexChange: (position) => {
7751
+ const picture = pictures[position];
7752
+ if (picture) setViewing({ entry: picture.entry, opener: viewing.opener });
7753
+ },
7754
+ onClose: () => setViewing(null),
7755
+ returnFocus: returnFocus(viewing.opener)
7756
+ }
7757
+ )
7758
+ ] })
7759
+ );
7760
+ }
7761
+ function ViewerFileActions({ value, src }) {
7762
+ const file = useStuffFile("image", value);
7763
+ const content = readImageContent(value);
7764
+ return /* @__PURE__ */ jsx(FileActions, { copy: { url: src, ...content ? { storageUrl: content.url } : {} }, file });
7274
7765
  }
7275
7766
  function FileRows({ items, kind }) {
7276
7767
  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: /* @__PURE__ */ jsx(ResultAt, { segment: String(index), children: kind === "document" ? /* @__PURE__ */ jsx(DocumentValue, { value: item }) : /* @__PURE__ */ jsx(ImageValue, { value: item, inRow: true }) }) }, index)) });
@@ -7598,7 +8089,7 @@ function ResultNode({
7598
8089
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header }),
7599
8090
  /* @__PURE__ */ jsx(CopyButton, { value: copyText, label: s.copyText })
7600
8091
  ] }),
7601
- isApp && field.kind === "text" && isLongText(unwrapped) ? /* @__PURE__ */ jsx(Markdown, { text: unwrapped }) : /* @__PURE__ */ jsx(LeafValue, { field, value })
8092
+ isApp && field.kind === "text" && isLongText(unwrapped) ? /* @__PURE__ */ jsx(Markdown, { text: unwrapped }) : field.kind === "document" && whole ? /* @__PURE__ */ jsx(DocumentValue, { value: unwrapped, whole: true }) : /* @__PURE__ */ jsx(LeafValue, { field, value })
7602
8093
  ] });
7603
8094
  }
7604
8095
  return null;
@@ -7859,6 +8350,6 @@ function pdfFileName(field, value, baseName) {
7859
8350
  return name === void 0 ? "" : name.replace(/\.[^.]+$/, "");
7860
8351
  }
7861
8352
 
7862
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EN_FIELD_STRINGS, EnumField, FIELD_STRINGS_BY_LOCALE, FR_FIELD_STRINGS, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, FilePicker, FilePickerEntry, FillWithExample, HtmlPreview, ImageField, JsonView, ListField, Markdown, MethodForm, NumberField, ObjectField, OptionalToggle, PdfDownloadButton, ProseField, ResultEnvProvider, ResultField, ResultRoot, SUPPORTED_LOCALES, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, checkedAccentClass, cn, fieldControlClass, fieldStringsFor, focusOutlineClass, humanizeFieldName, linkTextClass, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, selectedBorderClass, selectedFillClass, selectedFillStrongClass, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
7863
- //# sourceMappingURL=chunk-NA7SIQOH.js.map
7864
- //# sourceMappingURL=chunk-NA7SIQOH.js.map
8353
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EN_FIELD_STRINGS, EnumField, FIELD_STRINGS_BY_LOCALE, FR_FIELD_STRINGS, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, FilePicker, FilePickerEntry, FillWithExample, HtmlPreview, ImageField, JsonView, ListField, Markdown, MethodForm, NumberField, ObjectField, OptionalToggle, PdfDownloadButton, ProseField, ResultEnvProvider, ResultField, ResultRoot, SUPPORTED_LOCALES, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StuffViewer, Switch, TextField, ToggleGroup, ToggleGroupItem, UnknownField, checkedAccentClass, cn, fieldControlClass, fieldStringsFor, focusOutlineClass, humanizeFieldName, linkTextClass, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, selectedBorderClass, selectedFillClass, selectedFillStrongClass, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveRendition, useResolveShareUrl, useResolveUrl, useResolvedUrl };
8354
+ //# sourceMappingURL=chunk-RSYOZZZF.js.map
8355
+ //# sourceMappingURL=chunk-RSYOZZZF.js.map