@pipelex/mthds-form 0.21.0 → 0.23.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.
@@ -2,7 +2,7 @@ import { planFileSave, planStuffSave, splitFileName, typedFileStem } from './chu
2
2
  import { seedInputs, closeOptionalStructures, openOptionalStructures } from './chunk-VH5CDQDR.js';
3
3
  import { sourceFileValue, roomForChosenFiles, addChosenFiles, chosenFilesAdded } from './chunk-EQJBM3NS.js';
4
4
  import { readStuffFile } from './chunk-RRTI7AIR.js';
5
- import { setValueAtPath, apiInputsFromRunValues, sameRunValues, runValuesFromInputs } from './chunk-ZX52CQJ4.js';
5
+ import { apiInputsFromRunValues, sameRunValues, runValuesFromInputs, setValueAtPath } from './chunk-ZX52CQJ4.js';
6
6
  import { viewableUrl, isNativeDateNode, isNativeHtmlNode, readHtmlContent, isNativeCompositeNode, readCompositeContent, isNativeValueNode, readDateContent, formatDateContent, readImageContent, readDataUrl, readDocumentContent } from './chunk-CM4KVG53.js';
7
7
  import { buildRunFields } from './chunk-ZB4RYUKV.js';
8
8
  import { conceptCategory } from './chunk-FABY3DEY.js';
@@ -566,20 +566,12 @@ function FieldShell({
566
566
  ] });
567
567
  }
568
568
 
569
- // src/react/brand-ink.ts
570
- var brandInkClass = "text-[color:var(--brand,var(--primary,hsl(240_5.9%_10%)))]";
571
- var brandFillInkClass = "text-[color:var(--brand-muted-foreground,var(--brand,var(--primary,hsl(240_5.9%_10%))))]";
572
- var brandFillClass = "bg-[color:var(--brand-muted,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_10%,transparent))]";
573
- var brandFillSelectedClass = "data-[state=on]:bg-[color:var(--brand-muted,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_10%,transparent))]";
574
- var brandFillStrongClass = "bg-[color:var(--brand-muted,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_15%,transparent))]";
575
- var brandFocusClass = "focus-visible:border-[color:var(--brand,var(--primary,hsl(240_5.9%_10%)))] focus-visible:ring-[color:color-mix(in_oklab,var(--brand,var(--primary,hsl(240_5.9%_10%)))_40%,transparent)]";
576
-
577
569
  // src/react/field-styles.ts
578
570
  var fieldControlClass = cn(
579
571
  "w-full rounded-md border border-border bg-input text-sm text-foreground",
580
572
  "placeholder:text-muted-foreground/70 transition-colors",
581
573
  "focus-visible:outline-hidden focus-visible:ring-1",
582
- brandFocusClass,
574
+ "focus-visible:border-ring focus-visible:ring-ring/40",
583
575
  "disabled:cursor-not-allowed disabled:opacity-50",
584
576
  "aria-invalid:border-destructive aria-invalid:ring-destructive/30"
585
577
  );
@@ -612,6 +604,20 @@ function DateField({ field, value, onChange, id, error, disabled }) {
612
604
  );
613
605
  }
614
606
 
607
+ // src/react/role-tokens.ts
608
+ var linkTextClass = "text-[color:var(--link,var(--primary,hsl(240_5.9%_10%)))]";
609
+ var selectedFillClass = "bg-[color:var(--selected,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_10%,transparent))]";
610
+ var selectedStateFillClass = "data-[state=on]:bg-[color:var(--selected,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_10%,transparent))]";
611
+ var selectedCheckedFillClass = "aria-checked:bg-[color:var(--selected,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_10%,transparent))]";
612
+ var selectedFillStrongClass = "bg-[color:var(--selected,color-mix(in_oklab,var(--primary,hsl(240_5.9%_10%))_15%,transparent))]";
613
+ var selectedBorderClass = "border-[color:color-mix(in_oklab,var(--link,var(--primary,hsl(240_5.9%_10%)))_40%,transparent)]";
614
+ var selectedStateBorderClass = "data-[state=on]:border-[color:color-mix(in_oklab,var(--link,var(--primary,hsl(240_5.9%_10%)))_40%,transparent)]";
615
+ var selectedHoverBorderClass = "hover:border-[color:color-mix(in_oklab,var(--link,var(--primary,hsl(240_5.9%_10%)))_40%,transparent)]";
616
+ var checkedAccentClass = "accent-[color:var(--link,var(--primary,hsl(240_5.9%_10%)))]";
617
+ var focusOutlineClass = "focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring";
618
+ var focusOutlineInsetClass = "focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring";
619
+ var frameFocusOutlineClass = "data-focus-visible:outline-solid data-focus-visible:outline-2 data-focus-visible:-outline-offset-2 data-focus-visible:outline-ring";
620
+
615
621
  // src/react/encoded-file.ts
616
622
  function encodedFileSummary(url, strings) {
617
623
  const view = readDataUrl(url);
@@ -748,6 +754,7 @@ function ListAction({
748
754
  onClick,
749
755
  className: cn(
750
756
  "inline-flex min-h-11 items-center justify-center rounded-md border border-border px-4 text-[14px] text-foreground transition-colors hover:bg-muted sm:min-h-9",
757
+ focusOutlineClass,
751
758
  className
752
759
  ),
753
760
  children
@@ -838,12 +845,15 @@ var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @
838
845
  ));
839
846
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
840
847
  var toggleVariants = cva(
841
- "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
848
+ [
849
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:text-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
850
+ selectedStateFillClass
851
+ ],
842
852
  {
843
853
  variants: {
844
854
  variant: {
845
855
  default: "bg-transparent",
846
- outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground"
856
+ outline: "border border-border bg-transparent hover:bg-accent hover:text-accent-foreground"
847
857
  },
848
858
  size: {
849
859
  default: "h-10 px-3 min-w-10",
@@ -913,7 +923,10 @@ function FilePickerEntry({ onOpen, label, disabled, ref, id }) {
913
923
  disabled,
914
924
  "aria-haspopup": "dialog",
915
925
  "aria-label": s.chooseFromSourceAria(label),
916
- className: "flex min-h-11 w-full items-center justify-center gap-2 rounded-md border border-border px-3 text-[14px] text-foreground transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9",
926
+ className: cn(
927
+ "flex min-h-11 w-full items-center justify-center gap-2 rounded-md border border-border px-3 text-[14px] text-foreground transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9",
928
+ focusOutlineClass
929
+ ),
917
930
  children: [
918
931
  /* @__PURE__ */ jsx(Folder, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }),
919
932
  s.chooseFromSource
@@ -1025,7 +1038,10 @@ function FilePickerBody({
1025
1038
  },
1026
1039
  placeholder: s.filePickerSearchPlaceholder,
1027
1040
  autoComplete: "off",
1028
- className: "h-11 w-full rounded-md border border-border bg-input pr-3 pl-9 text-[16px] text-foreground placeholder:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring"
1041
+ className: cn(
1042
+ "h-11 w-full rounded-md border border-border bg-input pr-3 pl-9 text-[16px] text-foreground placeholder:text-muted-foreground",
1043
+ focusOutlineClass
1044
+ )
1029
1045
  }
1030
1046
  )
1031
1047
  ] }),
@@ -1091,7 +1107,10 @@ function FilePickerBody({
1091
1107
  type: "button",
1092
1108
  onClick: use6,
1093
1109
  disabled: choices.length === 0,
1094
- className: "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",
1110
+ className: cn(
1111
+ "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",
1112
+ focusOutlineClass
1113
+ ),
1095
1114
  children: /* @__PURE__ */ jsx("span", { className: "truncate", children: s.filePickerUse(choices.length, choices[0]?.name) })
1096
1115
  }
1097
1116
  ) })
@@ -1155,7 +1174,10 @@ function SheetHeader({
1155
1174
  DialogClose,
1156
1175
  {
1157
1176
  "aria-label": s.close,
1158
- className: "flex size-11 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
1177
+ className: cn(
1178
+ "flex size-11 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
1179
+ focusOutlineClass
1180
+ ),
1159
1181
  children: /* @__PURE__ */ jsx(X, { className: "size-5", "aria-hidden": "true" })
1160
1182
  }
1161
1183
  )
@@ -1201,9 +1223,9 @@ function FileRow({
1201
1223
  disabled,
1202
1224
  onClick: onToggle,
1203
1225
  className: cn(
1204
- "flex min-h-16 w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-muted/60 disabled:cursor-not-allowed disabled:opacity-50",
1205
- "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring",
1206
- chosen && "bg-muted"
1226
+ "group flex min-h-16 w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-muted/60 disabled:cursor-not-allowed disabled:opacity-50",
1227
+ focusOutlineInsetClass,
1228
+ selectedCheckedFillClass
1207
1229
  ),
1208
1230
  children: [
1209
1231
  /* @__PURE__ */ jsx(
@@ -1216,7 +1238,7 @@ function FileRow({
1216
1238
  ),
1217
1239
  /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1218
1240
  /* @__PURE__ */ jsx("span", { className: "block truncate text-[16px] font-medium text-foreground", children: file.name }),
1219
- (file.kind === "test" || meta) && /* @__PURE__ */ jsxs("span", { className: "block truncate text-[13px] text-muted-foreground", children: [
1241
+ (file.kind === "test" || meta) && /* @__PURE__ */ jsxs("span", { className: "block truncate text-[13px] text-muted-foreground group-aria-checked:text-foreground", children: [
1220
1242
  file.kind === "test" && /* @__PURE__ */ jsxs("span", { className: "text-foreground", children: [
1221
1243
  s.testFile,
1222
1244
  meta && " \xB7 "
@@ -1227,7 +1249,7 @@ function FileRow({
1227
1249
  /* @__PURE__ */ jsx(
1228
1250
  Check,
1229
1251
  {
1230
- className: cn("size-5 shrink-0 text-foreground", !chosen && "invisible"),
1252
+ className: "invisible size-5 shrink-0 text-foreground group-aria-checked:visible",
1231
1253
  "aria-hidden": "true"
1232
1254
  }
1233
1255
  )
@@ -1246,6 +1268,20 @@ function isReadableLink(url) {
1246
1268
  function dataUrlMime(url) {
1247
1269
  return DATA_URL_MIME_RE.exec(url)?.[1]?.toLowerCase();
1248
1270
  }
1271
+ function isThenable(answer) {
1272
+ return (typeof answer === "object" || typeof answer === "function") && answer !== null && typeof answer.then === "function";
1273
+ }
1274
+ function storedUrlOf(stored) {
1275
+ if (typeof stored === "string") return stored === "" ? void 0 : stored;
1276
+ if (stored === null || typeof stored !== "object") return void 0;
1277
+ const url = stored.url;
1278
+ return typeof url === "string" && url !== "" ? url : void 0;
1279
+ }
1280
+ function describesValue(local, url) {
1281
+ if (local.boundUrl !== void 0) return local.boundUrl === url;
1282
+ if (local.storedUrl !== void 0) return local.storedUrl === url;
1283
+ return !local.awaitingUpload;
1284
+ }
1249
1285
  function DocumentField(props) {
1250
1286
  return /* @__PURE__ */ jsx(FileField, { ...props, category: "document" });
1251
1287
  }
@@ -1274,6 +1310,9 @@ function FileField({
1274
1310
  const [typedUrl, setTypedUrl] = useState(null);
1275
1311
  const [previewOpen, setPreviewOpen] = useState(false);
1276
1312
  const [localPreview, setLocalPreview] = useState(null);
1313
+ const urlInFlight = useRef(void 0);
1314
+ const flightSeen = useRef(false);
1315
+ const localGeneration = useRef(0);
1277
1316
  const [resolved, setResolved] = useState(null);
1278
1317
  const localUrlRef = useRef(null);
1279
1318
  const [pickerOpen, setPickerOpen] = useState(false);
@@ -1282,6 +1321,7 @@ function FileField({
1282
1321
  const setLocal = useCallback((next) => {
1283
1322
  if (localUrlRef.current) URL.revokeObjectURL(localUrlRef.current);
1284
1323
  localUrlRef.current = next?.objectUrl ?? null;
1324
+ localGeneration.current += 1;
1285
1325
  setLocalPreview(next);
1286
1326
  }, []);
1287
1327
  useEffect(() => () => setLocal(null), [setLocal]);
@@ -1308,10 +1348,27 @@ function FileField({
1308
1348
  }
1309
1349
  setRejected(null);
1310
1350
  setDismissedUploadError(null);
1311
- setLocal({ objectUrl: URL.createObjectURL(file), type: file.type });
1312
- onDropFile(file);
1351
+ const upload = onDropFile(file);
1352
+ const awaitingUpload = isThenable(upload);
1353
+ setLocal({ objectUrl: URL.createObjectURL(file), type: file.type, awaitingUpload });
1354
+ urlInFlight.current = value?.url;
1355
+ flightSeen.current = false;
1356
+ if (!awaitingUpload) return;
1357
+ const generation = localGeneration.current;
1358
+ Promise.resolve(upload).then(
1359
+ (stored) => {
1360
+ if (localGeneration.current !== generation) return;
1361
+ const storedUrl = storedUrlOf(stored);
1362
+ setLocalPreview(
1363
+ (current) => current ? { ...current, awaitingUpload: false, storedUrl } : current
1364
+ );
1365
+ },
1366
+ () => {
1367
+ if (localGeneration.current === generation) setLocal(null);
1368
+ }
1369
+ );
1313
1370
  },
1314
- [formats, setLocal, onDropFile]
1371
+ [formats, setLocal, onDropFile, value?.url]
1315
1372
  );
1316
1373
  const busy = disabled || uploading;
1317
1374
  const {
@@ -1367,14 +1424,11 @@ function FileField({
1367
1424
  },
1368
1425
  [setLocal, onChange, dismissUploadError]
1369
1426
  );
1370
- const localIsCurrent = !!localPreview && (localPreview.boundUrl === void 0 || localPreview.boundUrl === value?.url);
1371
- const wasUploading = useRef(!!uploading);
1427
+ const localIsCurrent = !!localPreview && describesValue(localPreview, value?.url);
1372
1428
  useEffect(() => {
1373
- const ended = wasUploading.current && !uploading;
1374
- wasUploading.current = !!uploading;
1375
1429
  if (!localPreview || localPreview.boundUrl !== void 0) return;
1376
- if (failure !== null || ended && !value?.url) setLocal(null);
1377
- }, [uploading, failure, localPreview, value?.url, setLocal]);
1430
+ if (failure !== null) setLocal(null);
1431
+ }, [failure, localPreview, setLocal]);
1378
1432
  const lastUrl = useRef(value?.url);
1379
1433
  useEffect(() => {
1380
1434
  if (lastUrl.current === value?.url) return;
@@ -1383,12 +1437,30 @@ function FileField({
1383
1437
  }, [value?.url, uploadError]);
1384
1438
  useEffect(() => {
1385
1439
  if (!localPreview) return;
1386
- if (localPreview.boundUrl === void 0) {
1387
- if (uploading || !value?.url) return;
1388
- setLocalPreview({ ...localPreview, boundUrl: value.url });
1440
+ if (localPreview.boundUrl !== void 0) {
1441
+ if (value?.url !== localPreview.boundUrl) setLocal(null);
1389
1442
  return;
1390
1443
  }
1391
- if (value?.url !== localPreview.boundUrl) setLocal(null);
1444
+ if (uploading) {
1445
+ urlInFlight.current = value?.url;
1446
+ flightSeen.current = true;
1447
+ }
1448
+ if (localPreview.storedUrl !== void 0) {
1449
+ if (value?.url === localPreview.storedUrl) {
1450
+ setLocalPreview({ ...localPreview, boundUrl: localPreview.storedUrl });
1451
+ }
1452
+ return;
1453
+ }
1454
+ if (uploading || localPreview.awaitingUpload) return;
1455
+ if (!value?.url) {
1456
+ if (flightSeen.current) setLocal(null);
1457
+ return;
1458
+ }
1459
+ if (value.url === urlInFlight.current) {
1460
+ setLocal(null);
1461
+ return;
1462
+ }
1463
+ setLocalPreview({ ...localPreview, boundUrl: value.url });
1392
1464
  }, [localPreview, value?.url, uploading, setLocal]);
1393
1465
  const hasFile = !!value?.url;
1394
1466
  const localType = localIsCurrent ? localPreview?.type : void 0;
@@ -1549,7 +1621,11 @@ function FileField({
1549
1621
  type: "button",
1550
1622
  disabled: busy,
1551
1623
  onClick: () => setShowUrl(true),
1552
- className: "flex w-fit items-center gap-1 font-mono text-[11px] text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50 max-sm:min-h-11",
1624
+ className: cn(
1625
+ "flex w-fit items-center gap-1 rounded-sm font-mono text-[11px] text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50 max-sm:min-h-11",
1626
+ focusOutlineClass,
1627
+ "focus-visible:outline-offset-2"
1628
+ ),
1553
1629
  children: [
1554
1630
  /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" }),
1555
1631
  s.pasteUrlInstead
@@ -1659,7 +1735,10 @@ function FileChip({
1659
1735
  {
1660
1736
  type: "button",
1661
1737
  onClick: onTogglePreview,
1662
- className: "inline-flex h-7 shrink-0 items-center gap-1 rounded-md border border-border px-2 text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground max-sm:h-11 max-sm:px-3",
1738
+ className: cn(
1739
+ "inline-flex h-7 shrink-0 items-center gap-1 rounded-md border border-border px-2 text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground max-sm:h-11 max-sm:px-3",
1740
+ focusOutlineClass
1741
+ ),
1663
1742
  children: [
1664
1743
  previewOpen ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }),
1665
1744
  s.preview
@@ -1679,7 +1758,10 @@ function ClearButton({ disabled, onClick }) {
1679
1758
  onClick,
1680
1759
  disabled,
1681
1760
  "aria-label": s.removeFileAria,
1682
- className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50 max-sm:size-11",
1761
+ className: cn(
1762
+ "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50 max-sm:size-11",
1763
+ focusOutlineClass
1764
+ ),
1683
1765
  children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1684
1766
  }
1685
1767
  );
@@ -1824,14 +1906,14 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
1824
1906
  value: option,
1825
1907
  variant: "outline",
1826
1908
  className: cn(
1827
- "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]:font-medium data-[state=on]:text-foreground",
1828
- brandFillSelectedClass
1909
+ "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]:font-medium",
1910
+ selectedStateBorderClass
1829
1911
  ),
1830
1912
  children: [
1831
1913
  value === option && /* @__PURE__ */ jsx(
1832
1914
  Check,
1833
1915
  {
1834
- className: cn("size-3.5", brandFillInkClass),
1916
+ className: cn("size-3.5", linkTextClass),
1835
1917
  strokeWidth: 2.5,
1836
1918
  "aria-hidden": "true"
1837
1919
  }
@@ -1926,7 +2008,8 @@ function StepButton({
1926
2008
  "aria-label": label,
1927
2009
  className: cn(
1928
2010
  "flex h-11 w-11 shrink-0 items-center justify-center rounded-md border border-border bg-input text-muted-foreground transition-colors",
1929
- "hover:border-border hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
2011
+ "hover:border-border hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
2012
+ focusOutlineClass
1930
2013
  ),
1931
2014
  children
1932
2015
  }
@@ -2003,7 +2086,11 @@ function OptionalToggle({ count, expanded, onToggle, noun = "field" }) {
2003
2086
  type: "button",
2004
2087
  onClick: onToggle,
2005
2088
  "aria-expanded": expanded,
2006
- className: "flex w-fit items-center gap-1.5 text-[11px] text-muted-foreground transition-colors hover:text-foreground max-sm:min-h-11",
2089
+ className: cn(
2090
+ "flex w-fit items-center gap-1.5 rounded-sm text-[11px] text-muted-foreground transition-colors hover:text-foreground max-sm:min-h-11",
2091
+ focusOutlineClass,
2092
+ "focus-visible:outline-offset-2"
2093
+ ),
2007
2094
  children: [
2008
2095
  expanded ? /* @__PURE__ */ jsx(Minus, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
2009
2096
  expanded ? noun === "field" ? s.hideOptionalFields : s.hideOptionalInputs : noun === "field" ? s.optionalFieldsCount(count) : s.optionalInputsCount(count)
@@ -2102,12 +2189,28 @@ function firstFocusable(within) {
2102
2189
  }
2103
2190
  return null;
2104
2191
  }
2192
+
2193
+ // src/react/row-moves.ts
2194
+ function destinationOf(order) {
2195
+ return new Map(order.map((source, index) => [source, index]));
2196
+ }
2197
+ function movedId(id, listId, destination) {
2198
+ const prefix = `${listId}.`;
2199
+ if (!id.startsWith(prefix)) return id;
2200
+ const rest = id.slice(prefix.length);
2201
+ const dot = rest.indexOf(".");
2202
+ const row = dot === -1 ? rest : rest.slice(0, dot);
2203
+ if (!/^\d+$/.test(row)) return id;
2204
+ const index = destination.get(Number(row));
2205
+ if (index === void 0) return void 0;
2206
+ return `${prefix}${index}${dot === -1 ? "" : rest.slice(dot)}`;
2207
+ }
2105
2208
  function ListField({ field, value, onChange, id, error, env }) {
2106
2209
  const s = useFieldStrings();
2107
2210
  const presentation = useFieldPresentation();
2108
2211
  const isApp = presentation === "app";
2109
2212
  const items = value ?? [];
2110
- const [rowKeys, setRowKeys] = useRowKeys(items.length);
2213
+ const [rowKeys, setRowKeys] = useRowKeys(items.length, id, env?.rowMoves);
2111
2214
  const [pickerOpen, setPickerOpen] = useState(false);
2112
2215
  const pickerEntryRef = useRef(null);
2113
2216
  const rowsRef = useRef(null);
@@ -2119,7 +2222,12 @@ function ListField({ field, value, onChange, id, error, env }) {
2119
2222
  onChange(next);
2120
2223
  };
2121
2224
  const removeItem = (index) => {
2122
- setRowKeys((prev) => prev.filter((_, i) => i !== index));
2225
+ if (!env?.rowMoves) setRowKeys((prev) => prev.filter((_, i) => i !== index));
2226
+ const hostRemoves = env?.onRemoveItem;
2227
+ if (hostRemoves) {
2228
+ hostRemoves(id, index);
2229
+ return;
2230
+ }
2123
2231
  setStaleErrors((prev) => withRenumberedErrors(prev, id, index, env?.uploadErrors));
2124
2232
  onChange(items.filter((_, i) => i !== index));
2125
2233
  };
@@ -2150,7 +2258,7 @@ function ListField({ field, value, onChange, id, error, env }) {
2150
2258
  next.delete(dropId);
2151
2259
  return next;
2152
2260
  });
2153
- drop(dropId, file);
2261
+ return drop(dropId, file);
2154
2262
  } : void 0
2155
2263
  };
2156
2264
  }, [env, staleErrors]);
@@ -2206,7 +2314,10 @@ function ListField({ field, value, onChange, id, error, env }) {
2206
2314
  onClick: () => removeItem(index),
2207
2315
  disabled: env?.disabled || busy,
2208
2316
  "aria-label": s.removeItemAria(index + 1),
2209
- className: "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-destructive disabled:opacity-50 max-sm:mt-0 max-sm:size-11",
2317
+ className: cn(
2318
+ "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-destructive disabled:opacity-50 max-sm:mt-0 max-sm:size-11",
2319
+ focusOutlineClass
2320
+ ),
2210
2321
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
2211
2322
  }
2212
2323
  )
@@ -2222,7 +2333,11 @@ function ListField({ field, value, onChange, id, error, env }) {
2222
2333
  disabled: env?.disabled || isFull,
2223
2334
  className: cn(
2224
2335
  "inline-flex h-9 w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-border text-[13px] text-muted-foreground transition-colors max-sm:h-11",
2225
- "hover:border-primary/40 hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
2336
+ // The edge under the pointer is the accent's, as a selected pill's
2337
+ // is; the fill under it stays neutral.
2338
+ "hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
2339
+ selectedHoverBorderClass,
2340
+ focusOutlineClass
2226
2341
  ),
2227
2342
  children: [
2228
2343
  /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
@@ -2297,24 +2412,49 @@ function withRenumberedErrors(stale, listId, removed, hostErrors) {
2297
2412
  }
2298
2413
  return next.size === stale.size && [...next].every(([k, m]) => stale.get(k) === m) ? stale : next;
2299
2414
  }
2300
- function useRowKeys(length) {
2415
+ function useRowKeys(length, id, moves) {
2416
+ const latest = moves?.[moves.length - 1]?.seq ?? 0;
2301
2417
  const [state, setState] = useState(() => ({
2302
2418
  keys: Array.from({ length }, (_, i) => i),
2303
- next: length
2419
+ next: length,
2420
+ seq: latest,
2421
+ id
2304
2422
  }));
2305
- if (state.keys.length !== length) {
2306
- setState((prev) => {
2307
- if (length <= prev.keys.length) return { keys: prev.keys.slice(0, length), next: prev.next };
2308
- const added = length - prev.keys.length;
2309
- return {
2310
- keys: [...prev.keys, ...Array.from({ length: added }, (_, i) => prev.next + i)],
2311
- next: prev.next + added
2312
- };
2313
- });
2423
+ if (state.seq !== latest || state.id !== id || state.keys.length !== length) {
2424
+ setState((prev) => withLength(replayed(prev, moves, latest, id), length));
2314
2425
  }
2315
- const update = (fn) => setState((prev) => ({ keys: fn(prev.keys), next: prev.next }));
2426
+ const update = (fn) => setState((prev) => ({ ...prev, keys: fn(prev.keys) }));
2316
2427
  return [state.keys, update];
2317
2428
  }
2429
+ function replayed(state, moves, latest, id) {
2430
+ let { keys, next } = state;
2431
+ let tracked = state.id;
2432
+ for (const move of moves ?? []) {
2433
+ if (move.seq <= state.seq || tracked === void 0) continue;
2434
+ if (move.listId === tracked) {
2435
+ const before = keys;
2436
+ keys = [];
2437
+ for (const source of move.order) {
2438
+ const key = before[source];
2439
+ if (key === void 0) keys.push(next++);
2440
+ else keys.push(key);
2441
+ }
2442
+ } else {
2443
+ tracked = movedId(tracked, move.listId, destinationOf(move.order));
2444
+ }
2445
+ }
2446
+ return { keys, next, seq: latest, id };
2447
+ }
2448
+ function withLength(state, length) {
2449
+ if (state.keys.length === length) return state;
2450
+ if (length < state.keys.length) return { ...state, keys: state.keys.slice(0, length) };
2451
+ const added = length - state.keys.length;
2452
+ return {
2453
+ ...state,
2454
+ keys: [...state.keys, ...Array.from({ length: added }, (_, i) => state.next + i)],
2455
+ next: state.next + added
2456
+ };
2457
+ }
2318
2458
  function emptyValue(field) {
2319
2459
  switch (field.kind) {
2320
2460
  case "boolean":
@@ -2574,7 +2714,10 @@ var DropdownMenuItem = React5.forwardRef(({ className, ...props }, ref) => /* @_
2574
2714
  }
2575
2715
  ));
2576
2716
  DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
2577
- var buttonClass = "inline-flex min-h-11 items-center justify-center gap-2 rounded-md px-4 text-[14px] transition-colors disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9";
2717
+ var buttonClass = cn(
2718
+ "inline-flex min-h-11 items-center justify-center gap-2 rounded-md px-4 text-[14px] transition-colors disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9",
2719
+ focusOutlineClass
2720
+ );
2578
2721
  var secondaryClass = cn(buttonClass, "border border-border text-foreground hover:bg-muted");
2579
2722
  var primaryClass = cn(
2580
2723
  buttonClass,
@@ -2702,7 +2845,10 @@ function FillWithExample({
2702
2845
  {
2703
2846
  ref: closeButton,
2704
2847
  "aria-label": s.close,
2705
- className: "flex size-11 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2848
+ className: cn(
2849
+ "flex size-11 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2850
+ focusOutlineClass
2851
+ ),
2706
2852
  children: /* @__PURE__ */ jsx(X, { className: "size-5", "aria-hidden": "true" })
2707
2853
  }
2708
2854
  )
@@ -2716,7 +2862,10 @@ function FillWithExample({
2716
2862
  ref: register(rows.current, example.id),
2717
2863
  type: "button",
2718
2864
  onClick: () => choose(example),
2719
- className: "flex min-h-16 min-w-0 flex-1 items-center px-4 py-2.5 text-left transition-colors hover:bg-muted/60 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring",
2865
+ className: cn(
2866
+ "flex min-h-16 min-w-0 flex-1 items-center px-4 py-2.5 text-left transition-colors hover:bg-muted/60",
2867
+ focusOutlineInsetClass
2868
+ ),
2720
2869
  children: /* @__PURE__ */ jsx("span", { className: "truncate text-[16px] font-medium text-foreground", children: example.name })
2721
2870
  }
2722
2871
  ),
@@ -2728,7 +2877,10 @@ function FillWithExample({
2728
2877
  {
2729
2878
  ref: register(menus.current, example.id),
2730
2879
  "aria-label": s.exampleActions(example.name),
2731
- className: "flex size-11 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2880
+ className: cn(
2881
+ "flex size-11 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2882
+ focusOutlineInsetClass
2883
+ ),
2732
2884
  children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-5", "aria-hidden": "true" })
2733
2885
  }
2734
2886
  ),
@@ -2872,7 +3024,10 @@ function RenameExampleDialog({
2872
3024
  onKeyDown,
2873
3025
  disabled: state === "busy",
2874
3026
  autoComplete: "off",
2875
- className: "h-11 w-full rounded-md border border-border bg-input px-3 text-[16px] text-foreground focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring"
3027
+ className: cn(
3028
+ "h-11 w-full rounded-md border border-border bg-input px-3 text-[16px] text-foreground",
3029
+ focusOutlineClass
3030
+ )
2876
3031
  }
2877
3032
  )
2878
3033
  ] }),
@@ -2967,6 +3122,110 @@ function withoutId(ids, id) {
2967
3122
  next.delete(id);
2968
3123
  return next;
2969
3124
  }
3125
+ var NO_UPLOADS = /* @__PURE__ */ new Map();
3126
+ var NO_MOVES = [];
3127
+ var NOTHING_HELD = {
3128
+ edited: null,
3129
+ failed: EMPTY_IDS,
3130
+ uploads: NO_UPLOADS,
3131
+ moves: NO_MOVES
3132
+ };
3133
+ function withoutEntry(entries, id) {
3134
+ if (!entries.has(id)) return entries;
3135
+ const next = new Map(entries);
3136
+ next.delete(id);
3137
+ return next;
3138
+ }
3139
+ function fileUrlAt(values, id) {
3140
+ const node = valueAt(values, id);
3141
+ if (node === null || typeof node !== "object") return void 0;
3142
+ const url = ownProp(node, "url");
3143
+ return typeof url === "string" && url !== "" ? url : void 0;
3144
+ }
3145
+ function valueAt(values, id) {
3146
+ let node = values;
3147
+ for (const key of id.split(".")) {
3148
+ if (node === null || typeof node !== "object") return void 0;
3149
+ node = ownProp(node, key);
3150
+ }
3151
+ return node;
3152
+ }
3153
+ function written(held, initial, update) {
3154
+ const before = held.edited ?? initial;
3155
+ const after = update(before);
3156
+ if (after === held.edited) return held;
3157
+ const replaced = [...held.failed].filter((id) => fileUrlAt(before, id) !== fileUrlAt(after, id));
3158
+ if (replaced.length === 0) return { ...held, edited: after };
3159
+ const failed = new Set(held.failed);
3160
+ for (const id of replaced) failed.delete(id);
3161
+ return { ...held, edited: after, failed };
3162
+ }
3163
+ function uploadDropped(held, initial, id, token) {
3164
+ const uploads = new Map(held.uploads);
3165
+ uploads.set(id, { token, over: fileUrlAt(held.edited ?? initial, id) });
3166
+ return { ...held, failed: withoutId(held.failed, id), uploads };
3167
+ }
3168
+ function placeOf(held, token) {
3169
+ for (const entry of held.uploads) if (entry[1].token === token) return entry;
3170
+ return void 0;
3171
+ }
3172
+ function uploadFailed(held, initial, token) {
3173
+ const place = placeOf(held, token);
3174
+ if (!place) return held;
3175
+ const [id, upload] = place;
3176
+ const stillThere = fileUrlAt(held.edited ?? initial, id) === upload.over;
3177
+ return {
3178
+ ...held,
3179
+ failed: stillThere ? withId(held.failed, id) : held.failed,
3180
+ uploads: withoutEntry(held.uploads, id)
3181
+ };
3182
+ }
3183
+ function uploadLanded(held, initial, token, value) {
3184
+ const place = placeOf(held, token);
3185
+ if (!place) return held;
3186
+ const [id] = place;
3187
+ const next = written(held, initial, (before) => setValueAtPath(before, id.split("."), value));
3188
+ return { ...next, uploads: withoutEntry(next.uploads, id) };
3189
+ }
3190
+ function rowsMoved(held, initial, listId, order, seq) {
3191
+ const before = held.edited ?? initial;
3192
+ const rows = valueAt(before, listId);
3193
+ if (!Array.isArray(rows)) return held;
3194
+ const sources = order(rows);
3195
+ if (!sources) return held;
3196
+ const after = setValueAtPath(
3197
+ before,
3198
+ listId.split("."),
3199
+ sources.map((source) => rows[source])
3200
+ );
3201
+ const destination = destinationOf(sources);
3202
+ const failed = /* @__PURE__ */ new Set();
3203
+ for (const id of held.failed) {
3204
+ const moved = movedId(id, listId, destination);
3205
+ if (moved !== void 0) failed.add(moved);
3206
+ }
3207
+ const uploads = /* @__PURE__ */ new Map();
3208
+ for (const [id, upload] of held.uploads) {
3209
+ const moved = movedId(id, listId, destination);
3210
+ if (moved !== void 0) uploads.set(moved, upload);
3211
+ }
3212
+ const moves = [...held.moves, { seq, listId, order: sources }];
3213
+ return { edited: after, failed, uploads, moves };
3214
+ }
3215
+ function isRow(rows, index) {
3216
+ return Number.isInteger(index) && index >= 0 && index < rows.length;
3217
+ }
3218
+ function withoutRow(index) {
3219
+ return (rows) => isRow(rows, index) ? rows.map((_, i) => i).filter((i) => i !== index) : null;
3220
+ }
3221
+ function rowMovedTo(from, to) {
3222
+ return (rows) => {
3223
+ if (!isRow(rows, from) || !isRow(rows, to) || from === to) return null;
3224
+ const order = rows.map((_, i) => i).filter((i) => i !== from);
3225
+ order.splice(to, 0, from);
3226
+ return order;
3227
+ };
3228
+ }
2970
3229
  function orderFields(fields, order) {
2971
3230
  if (!order || order.length === 0) return fields;
2972
3231
  const rank = (field) => {
@@ -2995,39 +3254,71 @@ function useMethodForm({
2995
3254
  if (typeof initialValues === "function") return initialValues(seedInputs(fields));
2996
3255
  return initialValues ?? seedInputs(fields);
2997
3256
  }, [initialValues, fields]);
2998
- const [edited, setEdited] = useState(null);
3257
+ const store = useRef(NOTHING_HELD);
3258
+ const [held, setHeld] = useState(NOTHING_HELD);
3259
+ const commit = useCallback((next) => {
3260
+ if (next === store.current) return;
3261
+ store.current = next;
3262
+ setHeld(next);
3263
+ }, []);
3264
+ const edited = held.edited;
3265
+ const failedUploadIds = held.failed;
3266
+ const uploadingIds = useMemo(
3267
+ () => held.uploads.size === 0 ? EMPTY_IDS : new Set(held.uploads.keys()),
3268
+ [held.uploads]
3269
+ );
2999
3270
  const [given, setGiven] = useState(null);
3000
- const [uploadingIds, setUploadingIds] = useState(EMPTY_IDS);
3001
- const [failedUploadIds, setFailedUploadIds] = useState(EMPTY_IDS);
3002
3271
  const [attempted, setAttempted] = useState(false);
3003
3272
  const [resets, setResets] = useState(0);
3004
- const pendingUploads = useRef(/* @__PURE__ */ new Map());
3005
3273
  const uploadCounter = useRef(0);
3274
+ const moveCounter = useRef(0);
3006
3275
  const values = edited ?? initial;
3007
3276
  const setValues = useCallback(
3008
3277
  (next) => {
3009
3278
  setGiven((previous) => previous ?? initial);
3010
- setEdited((previous) => typeof next === "function" ? next(previous ?? initial) : next);
3279
+ commit(
3280
+ written(
3281
+ store.current,
3282
+ initial,
3283
+ (before) => typeof next === "function" ? next(before) : next
3284
+ )
3285
+ );
3286
+ },
3287
+ [initial, commit]
3288
+ );
3289
+ const moveRows = useCallback(
3290
+ (listId, order2) => {
3291
+ const seq = moveCounter.current + 1;
3292
+ const next = rowsMoved(store.current, initial, listId, order2, seq);
3293
+ if (next === store.current) return;
3294
+ moveCounter.current = seq;
3295
+ setGiven((previous) => previous ?? initial);
3296
+ commit(next);
3011
3297
  },
3012
- [initial]
3298
+ [initial, commit]
3299
+ );
3300
+ const removeItem = useCallback(
3301
+ (listId, index) => moveRows(listId, withoutRow(index)),
3302
+ [moveRows]
3303
+ );
3304
+ const moveItem = useCallback(
3305
+ (listId, from, to) => moveRows(listId, rowMovedTo(from, to)),
3306
+ [moveRows]
3307
+ );
3308
+ const reset = useCallback(
3309
+ (next) => {
3310
+ commit({ edited: next ?? null, failed: EMPTY_IDS, uploads: NO_UPLOADS, moves: NO_MOVES });
3311
+ setGiven(next ?? null);
3312
+ setAttempted(false);
3313
+ setResets((count) => count + 1);
3314
+ },
3315
+ [commit]
3013
3316
  );
3014
- const reset = useCallback((next) => {
3015
- pendingUploads.current.clear();
3016
- setEdited(next ?? null);
3017
- setGiven(next ?? null);
3018
- setUploadingIds(EMPTY_IDS);
3019
- setFailedUploadIds(EMPTY_IDS);
3020
- setAttempted(false);
3021
- setResets((count) => count + 1);
3022
- }, []);
3023
3317
  const dropFile = useMemo(() => {
3024
3318
  if (!uploadFile) return void 0;
3025
3319
  return (id, file) => {
3026
3320
  const token = ++uploadCounter.current;
3027
- pendingUploads.current.set(id, token);
3028
- const current = () => pendingUploads.current.get(id) === token;
3029
- setUploadingIds((previous) => withId(previous, id));
3030
- setFailedUploadIds((previous) => withoutId(previous, id));
3321
+ commit(uploadDropped(store.current, initial, id, token));
3031
3322
  const path = id.split(".");
3032
3323
  let upload;
3033
3324
  try {
@@ -3035,23 +3326,21 @@ function useMethodForm({
3035
3326
  } catch (error) {
3036
3327
  upload = Promise.reject(error);
3037
3328
  }
3038
- upload.then(
3039
- (stored) => {
3040
- if (!current()) return;
3041
- const value = typeof stored === "string" ? { url: stored, filename: file.name } : { url: stored.url, filename: stored.filename ?? file.name };
3042
- setGiven((previous) => previous ?? initial);
3043
- setEdited((previous) => setValueAtPath(previous ?? initial, path, value));
3044
- },
3045
- () => {
3046
- if (current()) setFailedUploadIds((previous) => withId(previous, id));
3329
+ const landed = upload.then((stored) => {
3330
+ if (!placeOf(store.current, token)) {
3331
+ throw new Error(
3332
+ `A newer file, a reset or the removal of its row replaced the upload dropped at "${id}".`
3333
+ );
3047
3334
  }
3048
- ).finally(() => {
3049
- if (!current()) return;
3050
- pendingUploads.current.delete(id);
3051
- setUploadingIds((previous) => withoutId(previous, id));
3335
+ const value = typeof stored === "string" ? { url: stored, filename: file.name } : { url: stored.url, filename: stored.filename ?? file.name };
3336
+ setGiven((previous) => previous ?? initial);
3337
+ commit(uploadLanded(store.current, initial, token, value));
3338
+ return value;
3052
3339
  });
3340
+ landed.then(void 0, () => commit(uploadFailed(store.current, initial, token)));
3341
+ return landed;
3053
3342
  };
3054
- }, [uploadFile, initial]);
3343
+ }, [uploadFile, initial, commit]);
3055
3344
  const readiness = useMemo(() => computeReadiness(fields, values), [fields, values]);
3056
3345
  const inputs = useMemo(
3057
3346
  () => apiInputsFromRunValues(values, fields, contract.inputs),
@@ -3077,6 +3366,9 @@ function useMethodForm({
3077
3366
  attempted,
3078
3367
  touched,
3079
3368
  setValues,
3369
+ removeItem,
3370
+ moveItem,
3371
+ rowMoves: held.moves,
3080
3372
  reset,
3081
3373
  attempt,
3082
3374
  resets,
@@ -3112,6 +3404,8 @@ function MethodFormFields({
3112
3404
  fields,
3113
3405
  values,
3114
3406
  setValues,
3407
+ removeItem,
3408
+ rowMoves,
3115
3409
  reset,
3116
3410
  attempted,
3117
3411
  touched,
@@ -3132,11 +3426,27 @@ function MethodFormFields({
3132
3426
  onDropFile: dropFile,
3133
3427
  uploadingIds,
3134
3428
  uploadErrors,
3429
+ // A list's remove button goes through the form, which carries a failed
3430
+ // upload, or one in flight, along with the rows it renumbers, and logs
3431
+ // the move for every list to move its rows' identities the same way,
3432
+ // whether the button or the host moved them.
3433
+ onRemoveItem: removeItem,
3434
+ rowMoves,
3135
3435
  allowUrl,
3136
3436
  resolveUrl,
3137
3437
  fileSource
3138
3438
  }),
3139
- [disabled, dropFile, uploadingIds, uploadErrors, allowUrl, resolveUrl, fileSource]
3439
+ [
3440
+ disabled,
3441
+ dropFile,
3442
+ uploadingIds,
3443
+ uploadErrors,
3444
+ removeItem,
3445
+ rowMoves,
3446
+ allowUrl,
3447
+ resolveUrl,
3448
+ fileSource
3449
+ ]
3140
3450
  );
3141
3451
  const folds = (field) => {
3142
3452
  const value = ownProp(values, field.name);
@@ -3678,6 +3988,83 @@ function ResultAt({ segment, children }) {
3678
3988
  );
3679
3989
  return /* @__PURE__ */ jsx(ResultLocationContext, { value: location, children });
3680
3990
  }
3991
+ function FocusFrame({ ref, className, ...props }) {
3992
+ const [frame, setFrame] = useState(null);
3993
+ const focused = useFrameFocus(frame);
3994
+ const attach = useCallback(
3995
+ (node) => {
3996
+ setFrame(node);
3997
+ if (typeof ref === "function") ref(node);
3998
+ else if (ref) ref.current = node;
3999
+ },
4000
+ [ref]
4001
+ );
4002
+ return /* @__PURE__ */ jsx(
4003
+ "iframe",
4004
+ {
4005
+ ...props,
4006
+ ref: attach,
4007
+ ...focused ? { "data-focus-visible": "" } : {},
4008
+ className: cn(className, frameFocusOutlineClass)
4009
+ }
4010
+ );
4011
+ }
4012
+ var frames = /* @__PURE__ */ new Set();
4013
+ var settling;
4014
+ var refresh = () => {
4015
+ for (const check of frames) check();
4016
+ };
4017
+ var onBlur = () => {
4018
+ refresh();
4019
+ clearTimeout(settling);
4020
+ settling = setTimeout(refresh, 0);
4021
+ };
4022
+ function listen(win) {
4023
+ win.addEventListener("blur", onBlur);
4024
+ win.addEventListener("focus", refresh);
4025
+ }
4026
+ function unlisten(win) {
4027
+ win.removeEventListener("blur", onBlur);
4028
+ win.removeEventListener("focus", refresh);
4029
+ }
4030
+ function useFrameFocus(frame) {
4031
+ const [focused, setFocused] = useState(false);
4032
+ useEffect(() => {
4033
+ if (!frame) return;
4034
+ const check = () => setFocused(document.hasFocus() && document.activeElement === frame);
4035
+ if (frames.size === 0) {
4036
+ listen(window);
4037
+ document.addEventListener("focusin", refresh);
4038
+ }
4039
+ frames.add(check);
4040
+ let own = null;
4041
+ const attachOwn = () => {
4042
+ try {
4043
+ own = frame.contentWindow;
4044
+ if (own) listen(own);
4045
+ } catch {
4046
+ own = null;
4047
+ }
4048
+ };
4049
+ attachOwn();
4050
+ frame.addEventListener("load", attachOwn);
4051
+ return () => {
4052
+ frame.removeEventListener("load", attachOwn);
4053
+ try {
4054
+ if (own) unlisten(own);
4055
+ } catch {
4056
+ }
4057
+ frames.delete(check);
4058
+ if (frames.size === 0) {
4059
+ unlisten(window);
4060
+ document.removeEventListener("focusin", refresh);
4061
+ clearTimeout(settling);
4062
+ settling = void 0;
4063
+ }
4064
+ };
4065
+ }, [frame]);
4066
+ return focused;
4067
+ }
3681
4068
  var TooltipProvider = TooltipPrimitive.Provider;
3682
4069
  var TooltipRoot = TooltipPrimitive.Root;
3683
4070
  var TooltipTrigger = TooltipPrimitive.Trigger;
@@ -3819,7 +4206,10 @@ function FileNameDialog({ stem: stem2, extension, onConfirm, onCancel }) {
3819
4206
  {
3820
4207
  type: "button",
3821
4208
  onClick: onCancel,
3822
- className: "rounded-md border border-border px-3 py-1 text-[12px] text-muted-foreground hover:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
4209
+ className: cn(
4210
+ "rounded-md border border-border px-3 py-1 text-[12px] text-muted-foreground hover:text-foreground",
4211
+ focusOutlineClass
4212
+ ),
3823
4213
  children: s.fileNameCancel
3824
4214
  }
3825
4215
  ),
@@ -3828,7 +4218,10 @@ function FileNameDialog({ stem: stem2, extension, onConfirm, onCancel }) {
3828
4218
  {
3829
4219
  type: "button",
3830
4220
  onClick: () => onConfirm(value),
3831
- className: "rounded-md bg-primary px-3 py-1 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
4221
+ className: cn(
4222
+ "rounded-md bg-primary px-3 py-1 text-[12px] font-medium text-primary-foreground hover:bg-primary/90",
4223
+ focusOutlineClass
4224
+ ),
3832
4225
  children: s.fileNameConfirm
3833
4226
  }
3834
4227
  )
@@ -4463,7 +4856,8 @@ function PdfDownloadButton({
4463
4856
  title: failed ? s.downloadPdfFailed : name,
4464
4857
  ...failed ? { "aria-describedby": statusId } : {},
4465
4858
  className: cn(
4466
- "inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 text-[12px] font-medium focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-70",
4859
+ "inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 text-[12px] font-medium disabled:opacity-70",
4860
+ focusOutlineClass,
4467
4861
  primary ? "bg-primary text-primary-foreground hover:bg-primary/90" : "border border-border text-muted-foreground hover:text-foreground",
4468
4862
  failed && (primary ? "bg-destructive text-white hover:bg-destructive/90" : "text-destructive"),
4469
4863
  className
@@ -4581,28 +4975,31 @@ function HtmlPreview({
4581
4975
  ]
4582
4976
  }
4583
4977
  )
4584
- ) : doc === null ? null : /* @__PURE__ */ jsx(
4585
- "iframe",
4586
- {
4587
- ref: frameRef,
4588
- title: content.cssClass ? `HTML result (${content.cssClass})` : "HTML result",
4589
- srcDoc: doc,
4590
- onLoad,
4591
- sandbox: "allow-same-origin",
4592
- style: {
4593
- display: "block",
4594
- width: "100%",
4595
- border: 0,
4596
- height: height === 0 ? void 0 : limit === void 0 ? height : Math.min(height, limit),
4597
- ...limit === void 0 ? {} : { maxHeight: limit },
4598
- // Its height is the page's, as a flex basis: in a bounded flex
4599
- // column it shrinks to what is left (never below the floor) and the
4600
- // page scrolls inside the frame; anywhere else it is the page's.
4601
- // `minHeight` is explicit because a replaced element's automatic
4602
- // minimum is its own height, which would forbid the shrink.
4603
- ...filling && height > 0 ? { flex: "0 1 auto", minHeight: Math.min(height, FILL_MIN_HEIGHT) } : {}
4978
+ ) : doc === null ? null : (
4979
+ // A tab stop, whose keyboard focus `FocusFrame` draws.
4980
+ /* @__PURE__ */ jsx(
4981
+ FocusFrame,
4982
+ {
4983
+ ref: frameRef,
4984
+ title: content.cssClass ? `HTML result (${content.cssClass})` : "HTML result",
4985
+ srcDoc: doc,
4986
+ onLoad,
4987
+ sandbox: "allow-same-origin",
4988
+ style: {
4989
+ display: "block",
4990
+ width: "100%",
4991
+ border: 0,
4992
+ height: height === 0 ? void 0 : limit === void 0 ? height : Math.min(height, limit),
4993
+ ...limit === void 0 ? {} : { maxHeight: limit },
4994
+ // Its height is the page's, as a flex basis: in a bounded flex
4995
+ // column it shrinks to what is left (never below the floor) and the
4996
+ // page scrolls inside the frame; anywhere else it is the page's.
4997
+ // `minHeight` is explicit because a replaced element's automatic
4998
+ // minimum is its own height, which would forbid the shrink.
4999
+ ...filling && height > 0 ? { flex: "0 1 auto", minHeight: Math.min(height, FILL_MIN_HEIGHT) } : {}
5000
+ }
4604
5001
  }
4605
- }
5002
+ )
4606
5003
  )
4607
5004
  }
4608
5005
  );
@@ -4812,7 +5209,7 @@ function InlineToken({ token }) {
4812
5209
  href,
4813
5210
  target: "_blank",
4814
5211
  rel: "noreferrer noopener",
4815
- className: "underline underline-offset-2",
5212
+ className: cn("rounded-sm underline underline-offset-2", focusOutlineClass),
4816
5213
  children: /* @__PURE__ */ jsx(Inline, { tokens: link.tokens })
4817
5214
  }
4818
5215
  ) })
@@ -4847,7 +5244,7 @@ function ProseImage({ token }) {
4847
5244
  href: src,
4848
5245
  target: "_blank",
4849
5246
  rel: "noreferrer noopener",
4850
- className: "underline underline-offset-2",
5247
+ className: cn("rounded-sm underline underline-offset-2", focusOutlineClass),
4851
5248
  children: token.text || src
4852
5249
  }
4853
5250
  );
@@ -5036,7 +5433,8 @@ function CopyButton({
5036
5433
  ...title ? { title } : {},
5037
5434
  "aria-label": label,
5038
5435
  className: cn(
5039
- "shrink-0 rounded p-0.5 text-muted-foreground hover:bg-card hover:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
5436
+ "shrink-0 rounded p-0.5 text-muted-foreground hover:bg-card hover:text-foreground",
5437
+ focusOutlineClass,
5040
5438
  FILE_BUTTON_ON_A_PHONE
5041
5439
  ),
5042
5440
  children: copied ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Copy, { "aria-hidden": true, className: "size-3.5" })
@@ -5076,7 +5474,10 @@ function FileRef({
5076
5474
  target: "_blank",
5077
5475
  rel: "noreferrer",
5078
5476
  title,
5079
- className: "min-w-0 truncate font-mono text-[12px] text-foreground underline underline-offset-2 max-sm:min-w-11 max-sm:leading-11",
5477
+ className: cn(
5478
+ "min-w-0 truncate rounded-sm font-mono text-[12px] text-foreground underline underline-offset-2 max-sm:min-w-11 max-sm:leading-11",
5479
+ focusOutlineClass
5480
+ ),
5080
5481
  children: label
5081
5482
  }
5082
5483
  ) : /* @__PURE__ */ jsx(
@@ -5157,7 +5558,8 @@ ${planned.url ?? planned.text}`;
5157
5558
  title: failed ? s.downloadFileFailed : label,
5158
5559
  ...failed ? { "aria-describedby": statusId } : {},
5159
5560
  className: cn(
5160
- "shrink-0 rounded p-0.5 hover:bg-card focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-60",
5561
+ "shrink-0 rounded p-0.5 hover:bg-card disabled:opacity-60",
5562
+ focusOutlineClass,
5161
5563
  FILE_BUTTON_ON_A_PHONE,
5162
5564
  failed ? "text-destructive" : "text-muted-foreground hover:text-foreground"
5163
5565
  ),
@@ -5193,7 +5595,7 @@ function previewableUrl(content, resolve) {
5193
5595
  }
5194
5596
  function DocumentPreview({ url, name }) {
5195
5597
  return /* @__PURE__ */ jsx(
5196
- "iframe",
5598
+ FocusFrame,
5197
5599
  {
5198
5600
  src: url,
5199
5601
  title: name,
@@ -5235,7 +5637,10 @@ function DocumentValue({ value }) {
5235
5637
  type: "button",
5236
5638
  onClick: () => setOpen((current) => !current),
5237
5639
  "aria-expanded": open,
5238
- 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-solid focus-visible:outline-2 focus-visible:outline-offset-1 max-sm:min-h-11 max-sm:px-3",
5640
+ className: cn(
5641
+ "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 max-sm:min-h-11 max-sm:px-3",
5642
+ focusOutlineClass
5643
+ ),
5239
5644
  children: [
5240
5645
  open ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": true, className: "size-3.5" }),
5241
5646
  s.preview
@@ -5271,7 +5676,10 @@ function ImageValue({
5271
5676
  target: "_blank",
5272
5677
  rel: "noreferrer",
5273
5678
  title: content.url,
5274
- className: inGallery || compact ? "inline-block" : "block w-full",
5679
+ className: cn(
5680
+ inGallery || compact ? "inline-block" : "block w-full",
5681
+ focusOutlineInsetClass
5682
+ ),
5275
5683
  children: /* @__PURE__ */ jsx(
5276
5684
  LoadingImage,
5277
5685
  {
@@ -5456,7 +5864,10 @@ function isInlineColumn(field) {
5456
5864
  var CELL_LENGTH_LIMIT = 80;
5457
5865
  var EXPAND_COLUMN = "var(--expand-column,2rem)";
5458
5866
  var EXPAND_COLUMN_WIDTH = "[--expand-column:2rem] max-sm:[--expand-column:3.25rem]";
5459
- var TOGGLE = "mt-0.5 flex size-6 items-center justify-center rounded text-muted-foreground hover:bg-card hover:text-foreground focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 max-sm:mt-0 max-sm:size-11";
5867
+ var TOGGLE = cn(
5868
+ "mt-0.5 flex size-6 items-center justify-center rounded text-muted-foreground hover:bg-card hover:text-foreground max-sm:mt-0 max-sm:size-11",
5869
+ focusOutlineClass
5870
+ );
5460
5871
  var STACK_NEVER_UP_TO = 3;
5461
5872
  var STACK_ALWAYS_BEYOND = 6;
5462
5873
  function stackingFor(columns) {
@@ -5600,6 +6011,7 @@ function ObjectTable({
5600
6011
  ...fits ? {} : { tabIndex: 0 },
5601
6012
  className: cn(
5602
6013
  "overflow-x-auto rounded-lg border border-border",
6014
+ focusOutlineInsetClass,
5603
6015
  EXPAND_COLUMN_WIDTH,
5604
6016
  fits && "@container"
5605
6017
  ),
@@ -6027,7 +6439,10 @@ function JsonView({ value }) {
6027
6439
  void navigator.clipboard.writeText(text).then(() => setCopied(true));
6028
6440
  },
6029
6441
  "aria-label": s.copyJson,
6030
- 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",
6442
+ className: cn(
6443
+ "absolute right-2 top-2 rounded border border-border bg-card p-1 text-muted-foreground hover:text-foreground",
6444
+ focusOutlineClass
6445
+ ),
6031
6446
  children: copied ? /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3.5" }) : /* @__PURE__ */ jsx(Copy, { "aria-hidden": true, className: "size-3.5" })
6032
6447
  }
6033
6448
  ),
@@ -6157,7 +6572,10 @@ function StuffPanel({
6157
6572
  onClick: () => void handleDownload(),
6158
6573
  disabled: savingShown,
6159
6574
  "aria-label": page ? s.downloadHtml : s.download,
6160
- 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-solid focus-visible:outline-2 focus-visible:outline-offset-1 disabled:opacity-60",
6575
+ className: cn(
6576
+ "inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-[12px] text-muted-foreground hover:text-foreground disabled:opacity-60",
6577
+ focusOutlineClass
6578
+ ),
6161
6579
  children: [
6162
6580
  savingShown ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }),
6163
6581
  savingShown ? s.downloading : page ? s.downloadHtml : s.download
@@ -6178,7 +6596,8 @@ function StuffPanel({
6178
6596
  onClick: () => setView(id),
6179
6597
  "aria-pressed": view === id,
6180
6598
  className: cn(
6181
- "rounded px-2 py-0.5 text-[12px] focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1",
6599
+ "rounded px-2 py-0.5 text-[12px]",
6600
+ focusOutlineClass,
6182
6601
  view === id ? "bg-card font-medium text-foreground" : "text-muted-foreground hover:text-foreground"
6183
6602
  ),
6184
6603
  children: labels[id]
@@ -6216,6 +6635,6 @@ function pdfFileName(field, value, baseName) {
6216
6635
  return name === void 0 ? "" : name.replace(/\.[^.]+$/, "");
6217
6636
  }
6218
6637
 
6219
- 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, brandFillClass, brandFillInkClass, brandFillStrongClass, brandInkClass, cn, fieldControlClass, fieldStringsFor, humanizeFieldName, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
6220
- //# sourceMappingURL=chunk-3XDR2C4I.js.map
6221
- //# sourceMappingURL=chunk-3XDR2C4I.js.map
6638
+ 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 };
6639
+ //# sourceMappingURL=chunk-NOSHTDID.js.map
6640
+ //# sourceMappingURL=chunk-NOSHTDID.js.map