@pipelex/mthds-form 0.23.1 → 0.24.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.
@@ -24,6 +24,29 @@ function addChosenFiles(held, chosen) {
24
24
  }
25
25
  return items;
26
26
  }
27
+ function placeDroppedFiles(held, count, maxItemCount, isFree = () => true) {
28
+ const items = [...held ?? []];
29
+ const targets = [];
30
+ for (let index = 0; index < items.length && targets.length < count; index += 1) {
31
+ if (heldUrl(items[index]) === void 0 && isFree(index)) targets.push(index);
32
+ }
33
+ let appended = 0;
34
+ while (targets.length < count && (maxItemCount === void 0 || items.length < maxItemCount)) {
35
+ targets.push(items.length);
36
+ items.push(void 0);
37
+ appended += 1;
38
+ }
39
+ return { items, appended, targets, leftOut: count - targets.length };
40
+ }
41
+ function roomForDroppedFiles(held, maxItemCount, isFree = () => true) {
42
+ if (maxItemCount === void 0) return void 0;
43
+ const items = held ?? [];
44
+ let free = 0;
45
+ for (let index = 0; index < items.length; index += 1) {
46
+ if (heldUrl(items[index]) === void 0 && isFree(index)) free += 1;
47
+ }
48
+ return free + Math.max(0, maxItemCount - items.length);
49
+ }
27
50
  function chosenFilesAdded(held, chosen) {
28
51
  const urls = heldUrls(held ?? []);
29
52
  const before = urls.size;
@@ -36,6 +59,6 @@ function roomForChosenFiles(held, maxItemCount) {
36
59
  return Math.max(0, maxItemCount - files);
37
60
  }
38
61
 
39
- export { addChosenFiles, chosenFilesAdded, roomForChosenFiles, sourceFileValue };
40
- //# sourceMappingURL=chunk-EQJBM3NS.js.map
41
- //# sourceMappingURL=chunk-EQJBM3NS.js.map
62
+ export { addChosenFiles, chosenFilesAdded, placeDroppedFiles, roomForChosenFiles, roomForDroppedFiles, sourceFileValue };
63
+ //# sourceMappingURL=chunk-NSHGLM6A.js.map
64
+ //# sourceMappingURL=chunk-NSHGLM6A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/sources.ts"],"names":[],"mappings":"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","file":"chunk-NSHGLM6A.js","sourcesContent":["/**\n * The two optional host seams beside the upload: a **file source**, which lets\n * a file field take a file the host already stores, and an **example source**,\n * which lets a whole form take a set of values someone saved for the method.\n *\n * The kernel states the shapes and applies the answers; the host answers. It\n * knows nothing of any platform's routes, records or permissions: a host maps\n * its own list onto these shapes, and a host that gives neither seam gets the\n * form it had before. The controls that use them are `./react`'s\n * (`FilePicker`, `FillWithExample`), and docs/sources.md is the contract.\n *\n * Shapes and pure functions only, so a host's server can import the types it\n * answers with, and the rules that write an answer into a value are tested in\n * node. No React, no fetch. What a row SAYS is data here (`SourceFile`'s\n * `typeLabel` and `origin` are the host's words); how a row DRAWS is a\n * rendering, so the glyph a host draws on a row is `./react`'s\n * (`FilePickerSource.renderGlyph`), never a field of these shapes.\n */\n\n/** Whose files a file source lists: the person's own, or the whole team's. */\nexport type FileSourceScope = 'mine' | 'everyone';\n\n/** One page of a file source's list, as the picker asks for it. */\nexport interface FileSourceQuery {\n /** What the person searched for, trimmed; empty when they searched for nothing. */\n search: string;\n /** Whose files to list. */\n scope: FileSourceScope;\n /**\n * The MIME types the field accepts, after any narrowing a host applied\n * (`narrowFileFormats`), in the field's own order. The list must hold only\n * files of these types: the picker writes what is chosen straight into the\n * field, and a field that refuses a dropped file of another type must not\n * be handed one this way.\n */\n mediaTypes: readonly string[];\n /** Where to continue: absent for the first page, else the previous page's `nextCursor`. */\n cursor?: string;\n /** Aborted when the answer is no longer wanted: a newer search, a closed picker. */\n signal: AbortSignal;\n}\n\n/** One row of a file source's list: a file the host already stores. */\nexport interface SourceFile {\n /** The row's identity in the host's list, which a choice is kept by. */\n id: string;\n /**\n * The stored file's address, written into the field as its `url`, exactly as\n * an upload's result is. Several rows may share one address.\n */\n url: string;\n /** The file's name, shown on the row and written into the field as its `filename`. */\n name: string;\n /**\n * The file's MIME type, which picks the field's format the row's file is:\n * the row names that format's label (\"PNG\") unless `typeLabel` words the\n * type, and draws an image's glyph or a document's from it unless the\n * host draws its own.\n */\n mediaType?: string;\n /**\n * The file's type in the host's own words (\"Picture\", \"PDF document\"), said\n * on the row's second line in place of the format's label, so a row reads\n * as the host's own list of files does. An empty string counts as none.\n */\n typeLabel?: string;\n /** The file's size in bytes. */\n size?: number;\n /** How the file came to be. A `test` file is marked \"Test file\" at the head of its row's second line. */\n kind?: 'uploaded' | 'generated' | 'test';\n /** When the file was stored, as an ISO 8601 timestamp; the row says it as a date. */\n createdAt?: string;\n /**\n * Where the file came from, in the host's own words (\"Uploaded by Ana\",\n * \"Made by Invoice extraction\"), shown on the row's second line as it is.\n */\n origin?: string;\n}\n\n/** One page of a file source's answer. */\nexport interface FileSourcePage {\n /** The page's files, newest first. A page may be empty and still carry a cursor. */\n files: readonly SourceFile[];\n /** Where the next page starts, or nothing when this page is the last. */\n nextCursor?: string | null;\n}\n\n/**\n * Where a file field can take a file the host already stores. When a form has\n * one, every file field offers \"Choose from your team's files\", and a list of\n * files offers it for several at once.\n */\nexport interface FileSource {\n /** Lists one page of files for a query. Rejecting shows the failure with Try again. */\n list: (query: FileSourceQuery) => Promise<FileSourcePage>;\n /** Which of Mine and Everyone the picker opens on. */\n initialScope: FileSourceScope;\n}\n\n/** One example of a method: a name and the inputs a run of it sends. */\nexport interface FormExample {\n /** The example's identity in the host's list. */\n id: string;\n /** The example's name, shown on its row. */\n name: string;\n /**\n * The inputs as a run takes them: each input `{ concept, content }`, an\n * optional input left out, an empty plural input a bare `[]`. It is the\n * shape `apiInputsFromRunValues` produces, so a host saves an example from\n * `form.inputs` and hands it back as it is.\n */\n inputs: Readonly<Record<string, unknown>>;\n}\n\n/** One page of an example source's list. */\nexport interface ExampleSourceQuery {\n /** Where to continue: absent for the first page, else the previous page's `nextCursor`. */\n cursor?: string;\n /** Aborted when the answer is no longer wanted. */\n signal: AbortSignal;\n}\n\n/** One page of an example source's answer. */\nexport interface ExampleSourcePage {\n /** The page's examples, in the order the list shows them. */\n examples: readonly FormExample[];\n /** Where the next page starts, or nothing when this page is the last. */\n nextCursor?: string | null;\n}\n\n/**\n * Where a form can take a whole set of values. When a form has one, it offers\n * \"Fill with an example\". `rename` and `delete` are capabilities: each is\n * offered on a row's menu only when given, so a host gives them only to a\n * person allowed to change examples.\n */\nexport interface ExampleSource {\n /** Lists one page of the method's examples. Rejecting shows the failure with Try again. */\n list: (query: ExampleSourceQuery) => Promise<ExampleSourcePage>;\n /**\n * Renames an example. Resolving to the example as stored replaces the row;\n * resolving to nothing keeps the row under the new name.\n */\n rename?: (example: FormExample, name: string) => Promise<FormExample | void>;\n /** Deletes an example. Asked for only after the person confirmed it. */\n delete?: (example: FormExample) => Promise<void>;\n /**\n * Whether \"Fill with an example\" is left out while the method has no\n * example. `false` by default, when the empty list says how to make one.\n * A host sets it for a person who cannot save an example, to whom an empty\n * list has nothing to offer; the form then asks for the first page as it\n * mounts, to know whether to offer the control.\n */\n hideWhenEmpty?: boolean;\n}\n\n/** What a chosen file writes into its field: the same `{ url, filename }` an upload writes. */\nexport function sourceFileValue(file: SourceFile): { url: string; filename: string } {\n return { url: file.url, filename: file.name };\n}\n\n/** The `url` a list item holds, when it is a file value holding one. */\nfunction heldUrl(item: unknown): string | undefined {\n if (!item || typeof item !== 'object') return undefined;\n const url = (item as { url?: unknown }).url;\n return typeof url === 'string' && url !== '' ? url : undefined;\n}\n\n/** The addresses a list's items hold, each once. */\nfunction heldUrls(items: readonly unknown[]): Set<string> {\n return new Set(items.map(heldUrl).filter((url): url is string => url !== undefined));\n}\n\n/**\n * A list of files with the chosen files written into it, in the order they\n * were chosen: first into the rows that hold no file yet (a row the person\n * added and left empty), then as new rows after the last. A file whose address\n * the list already holds is not written again, and neither is a second choice\n * of one address: two rows of a source may name the same stored file. Returns\n * a new array and leaves `held` as it was.\n */\nexport function addChosenFiles(\n held: readonly unknown[] | undefined,\n chosen: readonly SourceFile[],\n): unknown[] {\n const items = [...(held ?? [])];\n const urls = heldUrls(items);\n let free = 0;\n for (const file of chosen) {\n if (urls.has(file.url)) continue;\n urls.add(file.url);\n while (free < items.length && heldUrl(items[free]) !== undefined) free += 1;\n if (free < items.length) items[free] = sourceFileValue(file);\n else items.push(sourceFileValue(file));\n free += 1;\n }\n return items;\n}\n\n/** Where `placeDroppedFiles` puts a batch of files, and what it leaves out. */\nexport interface DroppedFilesPlacement {\n /**\n * The list once the rows the batch needs are appended: `held` as it was,\n * then one empty row (`undefined`) per file that did not go into a free row.\n * A new array whenever rows are appended, and a copy of `held` otherwise.\n */\n items: unknown[];\n /** How many rows were appended, so a caller writes the list only when it grew. */\n appended: number;\n /** The row each placed file goes to, in the order the files were given. */\n targets: number[];\n /** How many files, from the end of the batch, found no room. */\n leftOut: number;\n}\n\n/**\n * Where a batch of `count` files dropped on a list of files goes: first into\n * the rows that are free, in index order, then into new rows after the last,\n * as far as `maxItemCount` allows. The files keep the order they were given,\n * and those beyond the room are left out from the end of the batch.\n *\n * A row is free when it holds no file (its value carries no `url`) and\n * `isFree` says so too. The caller's predicate is where it adds what only it\n * knows of a row: that a file is still arriving there, whose write-back the row\n * must be kept for, or that the host reports a failure there, the only record\n * that one of the person's files did not arrive. Without one, every row holding\n * no file is free.\n *\n * It mirrors `addChosenFiles`, so the two ways several files enter a list fill\n * it alike, and it knows nothing of `File`: the caller judges each file and\n * hands over how many it accepted.\n */\nexport function placeDroppedFiles(\n held: readonly unknown[] | undefined,\n count: number,\n maxItemCount: number | undefined,\n isFree: (index: number) => boolean = () => true,\n): DroppedFilesPlacement {\n const items = [...(held ?? [])];\n const targets: number[] = [];\n for (let index = 0; index < items.length && targets.length < count; index += 1) {\n if (heldUrl(items[index]) === undefined && isFree(index)) targets.push(index);\n }\n let appended = 0;\n while (targets.length < count && (maxItemCount === undefined || items.length < maxItemCount)) {\n targets.push(items.length);\n items.push(undefined);\n appended += 1;\n }\n return { items, appended, targets, leftOut: count - targets.length };\n}\n\n/**\n * How many files `placeDroppedFiles` can place in `held`: its free rows, by the\n * same test, and the rows its bound still allows after the last. `undefined`\n * when the list has no bound, since appending then never runs out.\n */\nexport function roomForDroppedFiles(\n held: readonly unknown[] | undefined,\n maxItemCount: number | undefined,\n isFree: (index: number) => boolean = () => true,\n): number | undefined {\n if (maxItemCount === undefined) return undefined;\n const items = held ?? [];\n let free = 0;\n for (let index = 0; index < items.length; index += 1) {\n if (heldUrl(items[index]) === undefined && isFree(index)) free += 1;\n }\n return free + Math.max(0, maxItemCount - items.length);\n}\n\n/**\n * How many files `addChosenFiles` writes for `chosen` into `held`: the choices\n * whose address the list does not hold yet, each address once. This is what a\n * choice spends of `roomForChosenFiles`, so a held file chosen again, or a\n * second row naming a chosen file, takes no room.\n */\nexport function chosenFilesAdded(\n held: readonly unknown[] | undefined,\n chosen: readonly SourceFile[],\n): number {\n const urls = heldUrls(held ?? []);\n const before = urls.size;\n for (const file of chosen) urls.add(file.url);\n return urls.size - before;\n}\n\n/**\n * How many files a list may still take from a source: its bound less the files\n * it holds, since `addChosenFiles` writes into its empty rows before adding\n * any. `undefined` when the list has no bound.\n */\nexport function roomForChosenFiles(\n held: readonly unknown[] | undefined,\n maxItemCount: number | undefined,\n): number | undefined {\n if (maxItemCount === undefined) return undefined;\n const files = (held ?? []).filter((item) => heldUrl(item) !== undefined).length;\n return Math.max(0, maxItemCount - files);\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { planFileSave, planStuffSave, splitFileName, typedFileStem } from './chunk-ICUEHNEH.js';
2
2
  import { seedInputs, closeOptionalStructures, openOptionalStructures } from './chunk-VH5CDQDR.js';
3
- import { sourceFileValue, roomForChosenFiles, addChosenFiles, chosenFilesAdded } from './chunk-EQJBM3NS.js';
3
+ import { sourceFileValue, roomForChosenFiles, roomForDroppedFiles, addChosenFiles, placeDroppedFiles, chosenFilesAdded } from './chunk-NSHGLM6A.js';
4
4
  import { readStuffFile } from './chunk-RRTI7AIR.js';
5
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';
@@ -31,6 +31,9 @@ import { marked } from 'marked';
31
31
  function cn(...inputs) {
32
32
  return twMerge(clsx(inputs));
33
33
  }
34
+ function isThenable(answer) {
35
+ return (typeof answer === "object" || typeof answer === "function") && answer !== null && typeof answer.then === "function";
36
+ }
34
37
  var CATEGORY_ICON = {
35
38
  text: Type,
36
39
  date: CalendarDays,
@@ -222,6 +225,9 @@ function formatFileDate(date) {
222
225
  if (days === 1) return "Yesterday";
223
226
  return inCurrentYear(date) ? DAY.format(date) : DAY_YEAR.format(date);
224
227
  }
228
+ function someNames(names, more) {
229
+ return AND.format(more > 0 ? [...names, `${more} more`] : names);
230
+ }
225
231
  var EN_FIELD_STRINGS = {
226
232
  optionalBadge: "optional",
227
233
  selectPlaceholder: "Select\u2026",
@@ -276,6 +282,18 @@ var EN_FIELD_STRINGS = {
276
282
  yes: "Yes",
277
283
  no: "No",
278
284
  unsupportedFileType: (formats) => `That file type is not supported. Accepted formats: ${formats}.`,
285
+ dropFilesOrBrowse: "Drop files or click to browse",
286
+ dropFilesToAdd: "Drop to add the files",
287
+ addFilesAria: (label) => label ? `Add files to ${label}` : "Add files",
288
+ filesAdded: (count) => count === 1 ? "1 file added" : `${count} files added`,
289
+ filesRefused: (names, more, formats) => `${names.length + more === 1 ? "Not added, because its" : "Not added, because their"} file type is not supported: ${someNames(names, more)}. Accepted formats: ${formats}.`,
290
+ filesLeftOut: (names, more, fitted) => {
291
+ const these = names.length + more === 1 ? "this file was" : "these files were";
292
+ const room = fitted === 0 ? "The list is full" : `The list had room for ${fitted === 1 ? "1 more file" : `${fitted} more files`}`;
293
+ return `${room}, so ${these} not added: ${someNames(names, more)}.`;
294
+ },
295
+ oneFileOnly: "This field takes one file.",
296
+ oneFileOnlyInList: "This field takes one file. Drop several files on the list's drop area.",
279
297
  hideOptionalFields: "Hide optional fields",
280
298
  hideOptionalInputs: "Hide optional inputs",
281
299
  optionalFieldsCount: (count) => count === 1 ? "1 optional field" : `${count} optional fields`,
@@ -377,6 +395,9 @@ function formatBytes2(bytes) {
377
395
  if (bytes < 1024 * 1024) return KILOBYTES.format(bytes / 1024);
378
396
  return MEGABYTES.format(bytes / (1024 * 1024));
379
397
  }
398
+ function someNames2(names, more) {
399
+ return AND2.format(more > 0 ? [...names, `${more} ${plural(more, "autre", "autres")}`] : names);
400
+ }
380
401
  var FR_FIELD_STRINGS = {
381
402
  optionalBadge: "facultatif",
382
403
  selectPlaceholder: "Choisir\u2026",
@@ -431,6 +452,18 @@ var FR_FIELD_STRINGS = {
431
452
  yes: "Oui",
432
453
  no: "Non",
433
454
  unsupportedFileType: (formats) => `Ce type de fichier n\u2019est pas pris en charge. Formats accept\xE9s\xA0: ${formats}.`,
455
+ dropFilesOrBrowse: "D\xE9posez des fichiers ou cliquez pour parcourir",
456
+ dropFilesToAdd: "D\xE9posez pour ajouter les fichiers",
457
+ addFilesAria: (label) => label ? `Ajouter des fichiers \xE0 ${label}` : "Ajouter des fichiers",
458
+ filesAdded: (count) => `${count} ${plural(count, "fichier ajout\xE9", "fichiers ajout\xE9s")}`,
459
+ filesRefused: (names, more, formats) => `${names.length + more === 1 ? "Non ajout\xE9, car son type n\u2019est pas pris en charge" : "Non ajout\xE9s, car leur type n\u2019est pas pris en charge"}\xA0: ${someNames2(names, more)}. Formats accept\xE9s\xA0: ${formats}.`,
460
+ filesLeftOut: (names, more, fitted) => {
461
+ const these = names.length + more === 1 ? "ce fichier n\u2019a donc pas \xE9t\xE9 ajout\xE9" : "ces fichiers n\u2019ont donc pas \xE9t\xE9 ajout\xE9s";
462
+ const room = fitted === 0 ? "La liste est pleine" : `La liste n\u2019avait de place que pour ${fitted} ${plural(fitted, "fichier", "fichiers")} de plus`;
463
+ return `${room}, ${these}\xA0: ${someNames2(names, more)}.`;
464
+ },
465
+ oneFileOnly: "Ce champ accepte un seul fichier.",
466
+ oneFileOnlyInList: "Ce champ accepte un seul fichier. D\xE9posez-en plusieurs dans la zone de d\xE9p\xF4t de la liste.",
434
467
  hideOptionalFields: "Masquer les champs facultatifs",
435
468
  hideOptionalInputs: "Masquer les entr\xE9es facultatives",
436
469
  optionalFieldsCount: (count) => `${count} ${plural(count, "champ facultatif", "champs facultatifs")}`,
@@ -1089,6 +1122,7 @@ function FilePickerBody({
1089
1122
  {
1090
1123
  file,
1091
1124
  formats,
1125
+ glyph: source.renderGlyph?.(file),
1092
1126
  role: multiple ? "checkbox" : "radio",
1093
1127
  chosen: isChosen,
1094
1128
  disabled: !isChosen && !fits([...choices, file]),
@@ -1192,7 +1226,8 @@ function rowFormat(file, formats) {
1192
1226
  }
1193
1227
  function rowMeta(s, file, format) {
1194
1228
  const parts = [];
1195
- if (format) parts.push(format.label);
1229
+ const type = file.typeLabel || format?.label;
1230
+ if (type) parts.push(type);
1196
1231
  if (file.size !== void 0) parts.push(s.fileSize(file.size));
1197
1232
  if (file.origin) parts.push(file.origin);
1198
1233
  if (file.createdAt) {
@@ -1201,9 +1236,13 @@ function rowMeta(s, file, format) {
1201
1236
  }
1202
1237
  return parts.join(" \xB7 ");
1203
1238
  }
1239
+ function drawsNothing(glyph) {
1240
+ return glyph === null || glyph === void 0 || typeof glyph === "boolean" || glyph === "";
1241
+ }
1204
1242
  function FileRow({
1205
1243
  file,
1206
1244
  formats,
1245
+ glyph,
1207
1246
  role,
1208
1247
  chosen,
1209
1248
  disabled,
@@ -1228,13 +1267,17 @@ function FileRow({
1228
1267
  selectedCheckedFillClass
1229
1268
  ),
1230
1269
  children: [
1231
- /* @__PURE__ */ jsx(
1270
+ drawsNothing(glyph) ? /* @__PURE__ */ jsx(
1232
1271
  Glyph,
1233
1272
  {
1234
1273
  className: "size-6 shrink-0 text-muted-foreground",
1235
1274
  strokeWidth: 1.5,
1236
1275
  "aria-hidden": "true"
1237
1276
  }
1277
+ ) : (
1278
+ // `flex` so an inline glyph sits on no text baseline, `shrink-0` so a
1279
+ // long name never narrows it; the size is the glyph's own.
1280
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-slot": "file-row-glyph", className: "flex shrink-0", children: glyph })
1238
1281
  ),
1239
1282
  /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1240
1283
  /* @__PURE__ */ jsx("span", { className: "block truncate text-[16px] font-medium text-foreground", children: file.name }),
@@ -1257,6 +1300,10 @@ function FileRow({
1257
1300
  }
1258
1301
  );
1259
1302
  }
1303
+ var ListDropAreaContext = createContext(null);
1304
+ function useListDropArea() {
1305
+ return useContext(ListDropAreaContext);
1306
+ }
1260
1307
  var IMAGE_EXT_RE = /\.(jpg|jpeg|png|gif|webp|bmp|svg|avif)(\?|$)/i;
1261
1308
  var PDF_EXT_RE = /\.pdf(\?|$)/i;
1262
1309
  var DATA_URL_MIME_RE = /^data:([^;,]+)/i;
@@ -1268,9 +1315,6 @@ function isReadableLink(url) {
1268
1315
  function dataUrlMime(url) {
1269
1316
  return DATA_URL_MIME_RE.exec(url)?.[1]?.toLowerCase();
1270
1317
  }
1271
- function isThenable(answer) {
1272
- return (typeof answer === "object" || typeof answer === "function") && answer !== null && typeof answer.then === "function";
1273
- }
1274
1318
  function storedUrlOf(stored) {
1275
1319
  if (typeof stored === "string") return stored === "" ? void 0 : stored;
1276
1320
  if (stored === null || typeof stored !== "object") return void 0;
@@ -1326,6 +1370,7 @@ function FileField({
1326
1370
  }, []);
1327
1371
  useEffect(() => () => setLocal(null), [setLocal]);
1328
1372
  const [rejected, setRejected] = useState(null);
1373
+ const dropArea = useListDropArea();
1329
1374
  const [dismissedUploadError, setDismissedUploadError] = useState(null);
1330
1375
  useEffect(() => {
1331
1376
  if (uploadError === void 0) setDismissedUploadError(null);
@@ -1339,19 +1384,11 @@ function FileField({
1339
1384
  const hint = acceptLabel(formats);
1340
1385
  const accept = useMemo(() => acceptMap(formats), [formats]);
1341
1386
  const canUpload = onDropFile !== void 0;
1342
- const handleFile = useCallback(
1343
- (file) => {
1344
- if (!onDropFile) return;
1345
- if (!isAcceptedFile(formats, file)) {
1346
- setRejected(file.name);
1347
- return;
1348
- }
1349
- setRejected(null);
1350
- setDismissedUploadError(null);
1351
- const upload = onDropFile(file);
1387
+ const track = useCallback(
1388
+ (file, upload, heldUrl) => {
1352
1389
  const awaitingUpload = isThenable(upload);
1353
1390
  setLocal({ objectUrl: URL.createObjectURL(file), type: file.type, awaitingUpload });
1354
- urlInFlight.current = value?.url;
1391
+ urlInFlight.current = heldUrl;
1355
1392
  flightSeen.current = false;
1356
1393
  if (!awaitingUpload) return;
1357
1394
  const generation = localGeneration.current;
@@ -1368,8 +1405,28 @@ function FileField({
1368
1405
  }
1369
1406
  );
1370
1407
  },
1371
- [formats, setLocal, onDropFile, value?.url]
1408
+ [setLocal]
1372
1409
  );
1410
+ const handleFile = useCallback(
1411
+ (file) => {
1412
+ if (!onDropFile) return;
1413
+ if (!isAcceptedFile(formats, file)) {
1414
+ setRejected({ kind: "type", name: file.name, over: value?.url });
1415
+ return;
1416
+ }
1417
+ setRejected(null);
1418
+ setDismissedUploadError(null);
1419
+ track(file, onDropFile(file), value?.url);
1420
+ },
1421
+ [formats, onDropFile, track, value?.url]
1422
+ );
1423
+ useEffect(() => {
1424
+ const placed = dropArea?.takePlaced(id);
1425
+ if (!placed) return;
1426
+ setRejected(null);
1427
+ setDismissedUploadError(null);
1428
+ track(placed.file, placed.upload, void 0);
1429
+ }, [dropArea, id, track]);
1373
1430
  const busy = disabled || uploading;
1374
1431
  const {
1375
1432
  getRootProps,
@@ -1393,17 +1450,22 @@ function FileField({
1393
1450
  // handlers on a `role="presentation"` div, and the element a keyboard or
1394
1451
  // voice-control user lands on is a generic with no role and no name.
1395
1452
  noKeyboard: true,
1396
- onDrop: (files) => {
1397
- const file = files[0];
1398
- if (file) handleFile(file);
1399
- },
1400
- // react-dropzone rejects a file its own matcher refuses without ever calling
1401
- // `onDrop`, so without this a wrong drop is silently swallowed - the control
1402
- // simply does nothing, which reads as a broken dropzone rather than a
1453
+ // Every drop and pick lands here, the files react-dropzone's own matcher
1454
+ // refused included. Those must be answered: left alone, a wrong drop is
1455
+ // silently swallowed, which reads as a broken dropzone rather than a
1403
1456
  // refused file.
1404
- onDropRejected: (rejections) => {
1457
+ onDrop: (accepted, rejections) => {
1458
+ if (accepted.length + rejections.length > 1) {
1459
+ setRejected({ kind: "batch", over: value?.url });
1460
+ return;
1461
+ }
1462
+ const file = accepted[0];
1463
+ if (file) {
1464
+ handleFile(file);
1465
+ return;
1466
+ }
1405
1467
  const name = rejections[0]?.file.name;
1406
- if (name) setRejected(name);
1468
+ if (name) setRejected({ kind: "type", name, over: value?.url });
1407
1469
  }
1408
1470
  });
1409
1471
  const clear = useCallback(() => {
@@ -1429,11 +1491,13 @@ function FileField({
1429
1491
  if (!localPreview || localPreview.boundUrl !== void 0) return;
1430
1492
  if (failure !== null) setLocal(null);
1431
1493
  }, [failure, localPreview, setLocal]);
1494
+ const refusal = rejected !== null && rejected.over === value?.url ? rejected : null;
1432
1495
  const lastUrl = useRef(value?.url);
1433
1496
  useEffect(() => {
1434
1497
  if (lastUrl.current === value?.url) return;
1435
1498
  lastUrl.current = value?.url;
1436
1499
  if (uploadError !== void 0) setDismissedUploadError(uploadError);
1500
+ setRejected(null);
1437
1501
  }, [value?.url, uploadError]);
1438
1502
  useEffect(() => {
1439
1503
  if (!localPreview) return;
@@ -1590,11 +1654,15 @@ function FileField({
1590
1654
  }
1591
1655
  )
1592
1656
  ),
1593
- !busy && (rejected ? /* @__PURE__ */ jsxs("p", { role: "alert", className: "text-[12px] text-destructive", children: [
1594
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: rejected }),
1657
+ !busy && (refusal ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[12px] text-destructive", children: refusal.kind === "type" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1658
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: refusal.name }),
1595
1659
  " \u2014 ",
1596
1660
  s.unsupportedFileType(hint)
1597
- ] }) : failure && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[12px] text-destructive", children: failure })),
1661
+ ] }) : dropArea?.open ? (
1662
+ // Pointed to only while it takes files: a full list's area is
1663
+ // shut, and would refuse the very drop this sends there.
1664
+ s.oneFileOnlyInList
1665
+ ) : s.oneFileOnly }) : failure && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[12px] text-destructive", children: failure })),
1598
1666
  linkOpen ? /* @__PURE__ */ jsx(
1599
1667
  "input",
1600
1668
  {
@@ -2189,6 +2257,76 @@ function firstFocusable(within) {
2189
2257
  }
2190
2258
  return null;
2191
2259
  }
2260
+ var NAMED = 3;
2261
+ function ListFileDrop({ label, formats, shut, onPlace }) {
2262
+ const s = useFieldStrings();
2263
+ const hint = acceptLabel(formats);
2264
+ const accept = useMemo(() => acceptMap(formats), [formats]);
2265
+ const [notice, setNotice] = useState(null);
2266
+ const [added, setAdded] = useState({ count: 0, seq: 0 });
2267
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
2268
+ multiple: true,
2269
+ disabled: shut,
2270
+ // Filters the OS picker; `isAcceptedFile` below is the enforcement, as on a
2271
+ // single field, because a drop bypasses the picker entirely.
2272
+ accept,
2273
+ noKeyboard: true,
2274
+ onDrop: (accepted, rejections) => {
2275
+ const refused = rejections.map((rejection) => rejection.file);
2276
+ const taken = [];
2277
+ for (const file of accepted) (isAcceptedFile(formats, file) ? taken : refused).push(file);
2278
+ const fitted = taken.length > 0 ? onPlace(taken) : 0;
2279
+ setNotice({
2280
+ refused: refused.map((file) => file.name),
2281
+ leftOut: taken.slice(fitted).map((file) => file.name),
2282
+ fitted
2283
+ });
2284
+ if (fitted > 0) setAdded((prev) => ({ count: fitted, seq: prev.seq + 1 }));
2285
+ }
2286
+ });
2287
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2288
+ /* @__PURE__ */ jsxs(
2289
+ "div",
2290
+ {
2291
+ ...getRootProps(),
2292
+ "data-list-file-drop": true,
2293
+ className: cn(
2294
+ "flex flex-col items-center justify-center gap-1 rounded-md border border-dashed px-4 py-4 text-center max-sm:min-h-11",
2295
+ "cursor-pointer select-none transition-colors",
2296
+ "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background",
2297
+ isDragActive ? "border-primary/60 bg-primary/5" : "border-border bg-input hover:border-border hover:bg-muted",
2298
+ shut && "cursor-not-allowed opacity-60"
2299
+ ),
2300
+ children: [
2301
+ /* @__PURE__ */ jsx(
2302
+ "input",
2303
+ {
2304
+ ...getInputProps({ tabIndex: 0 }),
2305
+ disabled: shut,
2306
+ "aria-label": s.addFilesAria(label)
2307
+ }
2308
+ ),
2309
+ /* @__PURE__ */ jsx(Upload, { className: "h-4 w-4 text-muted-foreground", strokeWidth: 1.75 }),
2310
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] text-foreground", children: isDragActive ? s.dropFilesToAdd : s.dropFilesOrBrowse }),
2311
+ hint && /* @__PURE__ */ jsx("p", { className: "font-mono text-[10.5px] text-muted-foreground", children: hint })
2312
+ ]
2313
+ }
2314
+ ),
2315
+ notice && (notice.refused.length > 0 || notice.leftOut.length > 0) && /* @__PURE__ */ jsxs("div", { role: "alert", className: "space-y-1 text-[12px] text-destructive", children: [
2316
+ notice.refused.length > 0 && /* @__PURE__ */ jsx("p", { children: s.filesRefused(
2317
+ notice.refused.slice(0, NAMED),
2318
+ Math.max(0, notice.refused.length - NAMED),
2319
+ hint
2320
+ ) }),
2321
+ notice.leftOut.length > 0 && /* @__PURE__ */ jsx("p", { children: s.filesLeftOut(
2322
+ notice.leftOut.slice(0, NAMED),
2323
+ Math.max(0, notice.leftOut.length - NAMED),
2324
+ notice.fitted
2325
+ ) })
2326
+ ] }),
2327
+ /* @__PURE__ */ jsx("p", { role: "status", className: "sr-only", children: /* @__PURE__ */ jsx("span", { children: added.count > 0 ? s.filesAdded(added.count) : "" }, added.seq) })
2328
+ ] });
2329
+ }
2192
2330
 
2193
2331
  // src/react/row-moves.ts
2194
2332
  function destinationOf(order) {
@@ -2216,6 +2354,10 @@ function ListField({ field, value, onChange, id, error, env }) {
2216
2354
  const rowsRef = useRef(null);
2217
2355
  const landedRow = useRef(null);
2218
2356
  const [staleErrors, setStaleErrors] = useState(NO_ERRORS);
2357
+ const [pending, setPending] = useState(NO_UPLOADS);
2358
+ const nextUpload = useRef(0);
2359
+ const rowMoves = env?.rowMoves;
2360
+ const arriving = useMemo(() => arrivingRows(pending, rowMoves), [pending, rowMoves]);
2219
2361
  const setItem = (index, itemValue) => {
2220
2362
  const next = items.slice();
2221
2363
  next[index] = itemValue;
@@ -2232,7 +2374,7 @@ function ListField({ field, value, onChange, id, error, env }) {
2232
2374
  onChange(items.filter((_, i) => i !== index));
2233
2375
  };
2234
2376
  const addItem = () => onChange([...items, emptyValue(field.item)]);
2235
- const busy = listIsBusy(id, env?.uploadingIds);
2377
+ const busy = listIsBusy(id, env?.uploadingIds) || arriving.size > 0;
2236
2378
  const hostErrors = env?.uploadErrors;
2237
2379
  useEffect(() => {
2238
2380
  setStaleErrors((prev) => {
@@ -2241,16 +2383,19 @@ function ListField({ field, value, onChange, id, error, env }) {
2241
2383
  return next.size === prev.size ? prev : next;
2242
2384
  });
2243
2385
  }, [hostErrors]);
2386
+ const fileItem = field.item.kind === "document" || field.item.kind === "image" ? field.item : null;
2387
+ const tracksUploads = fileItem !== null;
2244
2388
  const rowEnv = useMemo(() => {
2245
- if (!env || staleErrors.size === 0) return env;
2389
+ if (!env || staleErrors.size === 0 && !tracksUploads) return env;
2246
2390
  const drop = env.onDropFile;
2391
+ const uploadErrors = staleErrors.size === 0 ? env.uploadErrors : new Map(
2392
+ [...env.uploadErrors ?? []].filter(
2393
+ ([key, message]) => staleErrors.get(key) !== message
2394
+ )
2395
+ );
2247
2396
  return {
2248
2397
  ...env,
2249
- uploadErrors: new Map(
2250
- [...env.uploadErrors ?? []].filter(([key, message]) => staleErrors.get(key) !== message)
2251
- ),
2252
- // A new file at a position is a new attempt there, so what the host says
2253
- // about that position from now on is about the file that is there.
2398
+ uploadErrors,
2254
2399
  onDropFile: drop ? (dropId, file) => {
2255
2400
  setStaleErrors((prev) => {
2256
2401
  if (!prev.has(dropId)) return prev;
@@ -2258,14 +2403,70 @@ function ListField({ field, value, onChange, id, error, env }) {
2258
2403
  next.delete(dropId);
2259
2404
  return next;
2260
2405
  });
2261
- return drop(dropId, file);
2406
+ const upload = drop(dropId, file);
2407
+ if (tracksUploads && isThenable(upload)) {
2408
+ const token = nextUpload.current++;
2409
+ const seq = env.rowMoves?.[env.rowMoves.length - 1]?.seq ?? 0;
2410
+ setPending((prev) => new Map(prev).set(token, { id: dropId, seq }));
2411
+ const answered = () => setPending((prev) => withoutUpload(prev, token));
2412
+ void Promise.resolve(upload).then(answered, answered);
2413
+ }
2414
+ return upload;
2262
2415
  } : void 0
2263
2416
  };
2264
- }, [env, staleErrors]);
2417
+ }, [env, staleErrors, tracksUploads]);
2265
2418
  const isFull = field.maxItemCount !== void 0 && items.length >= field.maxItemCount;
2266
2419
  const fileSource = env?.fileSource;
2267
- const fileItem = field.item.kind === "document" || field.item.kind === "image" ? field.item : null;
2268
2420
  const room = roomForChosenFiles(items, field.maxItemCount);
2421
+ const dropFile = rowEnv?.onDropFile;
2422
+ const dropOffered = fileItem !== null && dropFile !== void 0;
2423
+ const isFreeRow = (index) => {
2424
+ const rowId = `${id}.${index}`;
2425
+ return !arriving.has(rowId) && !env?.uploadingIds?.has(rowId) && !rowEnv?.uploadErrors?.has(rowId);
2426
+ };
2427
+ const dropRoom = dropOffered ? roomForDroppedFiles(items, field.maxItemCount, isFreeRow) : 0;
2428
+ const dropOpen = dropOffered && !env?.disabled && dropRoom !== 0;
2429
+ const placed = useRef(/* @__PURE__ */ new Map());
2430
+ const [placements, setPlacements] = useState(0);
2431
+ const dropArea = useMemo(() => {
2432
+ if (!dropOffered) return null;
2433
+ return {
2434
+ generation: placements,
2435
+ open: dropOpen,
2436
+ takePlaced: (rowId) => {
2437
+ const file = placed.current.get(rowId);
2438
+ placed.current.delete(rowId);
2439
+ return file;
2440
+ }
2441
+ };
2442
+ }, [dropOffered, dropOpen, placements]);
2443
+ const dropRef = useRef(null);
2444
+ const landing = useRef(null);
2445
+ useEffect(() => {
2446
+ const landed = landing.current;
2447
+ if (landed === null) return;
2448
+ landing.current = null;
2449
+ const rows = rowsRef.current?.querySelectorAll(":scope > [data-list-row]");
2450
+ const target = firstFocusable(rows?.[landed]) ?? followingFocusable(dropRef.current, dropRef.current);
2451
+ target?.focus();
2452
+ });
2453
+ const placeFiles = (files) => {
2454
+ if (!dropFile) return 0;
2455
+ const placement = placeDroppedFiles(items, files.length, field.maxItemCount, isFreeRow);
2456
+ if (placement.appended > 0) onChange(placement.items);
2457
+ placement.targets.forEach((index, order) => {
2458
+ const file = files[order];
2459
+ if (!file) return;
2460
+ const rowId = `${id}.${index}`;
2461
+ placed.current.set(rowId, { file, upload: handOver(dropFile, rowId, file) });
2462
+ });
2463
+ const count = placement.targets.length;
2464
+ if (count > 0) setPlacements((generation) => generation + 1);
2465
+ if (count > 0 && count === dropRoom && dropRef.current?.contains(document.activeElement)) {
2466
+ landing.current = placement.targets[count - 1] ?? null;
2467
+ }
2468
+ return count;
2469
+ };
2269
2470
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2270
2471
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
2271
2472
  /* @__PURE__ */ jsx(
@@ -2284,8 +2485,8 @@ function ListField({ field, value, onChange, id, error, env }) {
2284
2485
  ] }),
2285
2486
  field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
2286
2487
  /* @__PURE__ */ jsxs("div", { ref: rowsRef, className: "space-y-2", children: [
2287
- items.length === 0 && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-dashed border-border bg-input/50 px-3 py-4 text-center text-[12px] text-muted-foreground", children: s.noItemsYet }),
2288
- items.map((item, index) => /* @__PURE__ */ jsxs(
2488
+ items.length === 0 && !dropOffered && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-dashed border-border bg-input/50 px-3 py-4 text-center text-[12px] text-muted-foreground", children: s.noItemsYet }),
2489
+ /* @__PURE__ */ jsx(ListDropAreaContext.Provider, { value: dropArea, children: items.map((item, index) => /* @__PURE__ */ jsxs(
2289
2490
  "div",
2290
2491
  {
2291
2492
  "data-list-row": true,
@@ -2324,7 +2525,16 @@ function ListField({ field, value, onChange, id, error, env }) {
2324
2525
  ]
2325
2526
  },
2326
2527
  rowKeys[index] ?? index
2327
- )),
2528
+ )) }),
2529
+ dropOffered && fileItem && /* @__PURE__ */ jsx("div", { ref: dropRef, children: /* @__PURE__ */ jsx(
2530
+ ListFileDrop,
2531
+ {
2532
+ label: fieldLabel(field.title, field.name, presentation),
2533
+ formats: fileItem.formats,
2534
+ shut: !dropOpen,
2535
+ onPlace: placeFiles
2536
+ }
2537
+ ) }),
2328
2538
  /* @__PURE__ */ jsxs(
2329
2539
  "button",
2330
2540
  {
@@ -2401,6 +2611,42 @@ function listIsBusy(id, uploadingIds) {
2401
2611
  return false;
2402
2612
  }
2403
2613
  var NO_ERRORS = /* @__PURE__ */ new Map();
2614
+ var NO_IDS = /* @__PURE__ */ new Set();
2615
+ var NO_UPLOADS = /* @__PURE__ */ new Map();
2616
+ function withoutUpload(uploads, token) {
2617
+ if (!uploads.has(token)) return uploads;
2618
+ const next = new Map(uploads);
2619
+ next.delete(token);
2620
+ return next;
2621
+ }
2622
+ function arrivingRows(uploads, moves) {
2623
+ if (uploads.size === 0) return NO_IDS;
2624
+ const rows = /* @__PURE__ */ new Set();
2625
+ for (const upload of uploads.values()) {
2626
+ let at = upload.id;
2627
+ for (const move of moves ?? []) {
2628
+ if (at === void 0) break;
2629
+ if (move.seq > upload.seq) at = movedId(at, move.listId, destinationOf(move.order));
2630
+ }
2631
+ if (at !== void 0) rows.add(at);
2632
+ }
2633
+ return rows;
2634
+ }
2635
+ function handOver(drop, rowId, file) {
2636
+ try {
2637
+ return drop(rowId, file);
2638
+ } catch (error) {
2639
+ reportHostError(error);
2640
+ const failed = Promise.reject(error);
2641
+ failed.catch(() => {
2642
+ });
2643
+ return failed;
2644
+ }
2645
+ }
2646
+ function reportHostError(error) {
2647
+ if (typeof globalThis.reportError === "function") globalThis.reportError(error);
2648
+ else console.error(error);
2649
+ }
2404
2650
  function withRenumberedErrors(stale, listId, removed, hostErrors) {
2405
2651
  if (!hostErrors?.size) return stale;
2406
2652
  const prefix = `${listId}.`;
@@ -3122,12 +3368,12 @@ function withoutId(ids, id) {
3122
3368
  next.delete(id);
3123
3369
  return next;
3124
3370
  }
3125
- var NO_UPLOADS = /* @__PURE__ */ new Map();
3371
+ var NO_UPLOADS2 = /* @__PURE__ */ new Map();
3126
3372
  var NO_MOVES = [];
3127
3373
  var NOTHING_HELD = {
3128
3374
  edited: null,
3129
3375
  failed: EMPTY_IDS,
3130
- uploads: NO_UPLOADS,
3376
+ uploads: NO_UPLOADS2,
3131
3377
  moves: NO_MOVES
3132
3378
  };
3133
3379
  function withoutEntry(entries, id) {
@@ -3307,7 +3553,7 @@ function useMethodForm({
3307
3553
  );
3308
3554
  const reset = useCallback(
3309
3555
  (next) => {
3310
- commit({ edited: next ?? null, failed: EMPTY_IDS, uploads: NO_UPLOADS, moves: NO_MOVES });
3556
+ commit({ edited: next ?? null, failed: EMPTY_IDS, uploads: NO_UPLOADS2, moves: NO_MOVES });
3311
3557
  setGiven(next ?? null);
3312
3558
  setAttempted(false);
3313
3559
  setResets((count) => count + 1);
@@ -6636,5 +6882,5 @@ function pdfFileName(field, value, baseName) {
6636
6882
  }
6637
6883
 
6638
6884
  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
6885
+ //# sourceMappingURL=chunk-UGKFWX7L.js.map
6886
+ //# sourceMappingURL=chunk-UGKFWX7L.js.map