@pipelex/mthds-form 0.35.1 → 0.36.1

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.
@@ -237,6 +237,9 @@ var EN_FIELD_STRINGS = {
237
237
  previewUnavailablePdf: "Preview unavailable - open to view.",
238
238
  pdfViewerUnavailable: "This browser cannot show the PDF here.",
239
239
  openInNewTab: "Open in a new tab",
240
+ documentPdfPreparing: "Preparing the preview\u2026",
241
+ documentPdfFailed: "Couldn\u2019t prepare a preview.",
242
+ documentPdfUnavailable: "No preview for this file.",
240
243
  // U+002D HYPHEN-MINUS, deliberately: not an en dash, not an em dash.
241
244
  resultAbsent: "-",
242
245
  resultAbsentDescription: "Not provided",
@@ -423,6 +426,9 @@ var FR_FIELD_STRINGS = {
423
426
  previewUnavailablePdf: "Aper\xE7u indisponible - ouvrez le fichier pour le consulter.",
424
427
  pdfViewerUnavailable: "Ce navigateur ne peut pas afficher le PDF ici.",
425
428
  openInNewTab: "Ouvrir dans un nouvel onglet",
429
+ documentPdfPreparing: "Pr\xE9paration de l\u2019aper\xE7u\u2026",
430
+ documentPdfFailed: "Impossible de pr\xE9parer un aper\xE7u.",
431
+ documentPdfUnavailable: "Aucun aper\xE7u pour ce fichier.",
426
432
  // U+002D HYPHEN-MINUS, as in the English defaults.
427
433
  resultAbsent: "-",
428
434
  resultAbsentDescription: "Non renseign\xE9",
@@ -4441,6 +4447,7 @@ function ResultEnvProvider({
4441
4447
  resolveUrl,
4442
4448
  resolveUrls,
4443
4449
  resolveRendition,
4450
+ resolveDocumentPdf,
4444
4451
  resolveShareUrl,
4445
4452
  proseImages,
4446
4453
  tableColumns,
@@ -4456,6 +4463,7 @@ function ResultEnvProvider({
4456
4463
  resolveUrl,
4457
4464
  resolveUrls,
4458
4465
  resolveRendition,
4466
+ resolveDocumentPdf,
4459
4467
  resolveShareUrl,
4460
4468
  proseImages,
4461
4469
  tableColumns,
@@ -4469,6 +4477,7 @@ function ResultEnvProvider({
4469
4477
  resolveUrl,
4470
4478
  resolveUrls,
4471
4479
  resolveRendition,
4480
+ resolveDocumentPdf,
4472
4481
  resolveShareUrl,
4473
4482
  proseImages,
4474
4483
  tableColumns,
@@ -4524,6 +4533,9 @@ function useMarkupResolvers() {
4524
4533
  function useResolveRendition() {
4525
4534
  return use(ResultEnvContext).resolveRendition;
4526
4535
  }
4536
+ function useResolveDocumentPdf() {
4537
+ return use(ResultEnvContext).resolveDocumentPdf;
4538
+ }
4527
4539
  function useResolveShareUrl() {
4528
4540
  return use(ResultEnvContext).resolveShareUrl;
4529
4541
  }
@@ -4631,6 +4643,74 @@ function ResultAt({ segment, children }) {
4631
4643
  );
4632
4644
  return /* @__PURE__ */ jsx(ResultLocationContext, { value: location, children });
4633
4645
  }
4646
+ var DOCUMENT_PDF_ASK_TIMEOUT_MS = 3e4;
4647
+ var DOCUMENT_PDF_PREPARING_LIMIT_MS = 18e4;
4648
+ var FIRST_WAIT_MS = 1500;
4649
+ var WAIT_GROWTH = 1.5;
4650
+ var LONGEST_WAIT_MS = 1e4;
4651
+ var PREPARING = { state: "preparing" };
4652
+ var FAILED = { state: "failed" };
4653
+ var isNonEmptyString = (value) => typeof value === "string" && value !== "";
4654
+ async function askOnce(ask) {
4655
+ let timer;
4656
+ const timeout = new Promise((resolve) => {
4657
+ timer = setTimeout(() => resolve(FAILED), DOCUMENT_PDF_ASK_TIMEOUT_MS);
4658
+ });
4659
+ try {
4660
+ const answer = await Promise.race([ask(), timeout]);
4661
+ switch (answer?.state) {
4662
+ case "ready":
4663
+ return isNonEmptyString(answer.url) ? answer : FAILED;
4664
+ case "failed":
4665
+ return isNonEmptyString(answer.message) ? answer : FAILED;
4666
+ case "preparing":
4667
+ case "unavailable":
4668
+ return answer;
4669
+ default:
4670
+ return FAILED;
4671
+ }
4672
+ } catch {
4673
+ return FAILED;
4674
+ } finally {
4675
+ clearTimeout(timer);
4676
+ }
4677
+ }
4678
+ function useDocumentPdf(ask) {
4679
+ const [answer, setAnswer] = useState(PREPARING);
4680
+ const [attempt, setAttempt] = useState(0);
4681
+ const latestAsk = useRef(ask);
4682
+ useEffect(() => {
4683
+ latestAsk.current = ask;
4684
+ });
4685
+ useEffect(() => {
4686
+ let stale = false;
4687
+ let timer;
4688
+ const started = Date.now();
4689
+ const askAgain = async (wait) => {
4690
+ const asked = await askOnce(latestAsk.current);
4691
+ if (stale) return;
4692
+ if (asked.state !== "preparing") {
4693
+ setAnswer(asked);
4694
+ return;
4695
+ }
4696
+ if (Date.now() + wait - started > DOCUMENT_PDF_PREPARING_LIMIT_MS) {
4697
+ setAnswer(FAILED);
4698
+ return;
4699
+ }
4700
+ timer = setTimeout(() => void askAgain(Math.min(wait * WAIT_GROWTH, LONGEST_WAIT_MS)), wait);
4701
+ };
4702
+ void askAgain(FIRST_WAIT_MS);
4703
+ return () => {
4704
+ stale = true;
4705
+ clearTimeout(timer);
4706
+ };
4707
+ }, [attempt]);
4708
+ const retry = useCallback(() => {
4709
+ setAnswer(PREPARING);
4710
+ setAttempt((current) => current + 1);
4711
+ }, []);
4712
+ return { answer, retry };
4713
+ }
4634
4714
  function FocusFrame({ ref, className, ...props }) {
4635
4715
  const [frame, setFrame] = useState(null);
4636
4716
  const focused = useFrameFocus(frame);
@@ -7088,11 +7168,12 @@ function FileRef({
7088
7168
  storageUrl,
7089
7169
  mimeType,
7090
7170
  filename,
7171
+ label: named,
7091
7172
  file
7092
7173
  }) {
7093
7174
  const s = useFieldStrings();
7094
7175
  const encoded = encodedFileSummary(url, s);
7095
- const label = filename ? filename : encoded ?? pathLabel(url);
7176
+ const label = named ?? (filename ? filename : encoded ?? pathLabel(url));
7096
7177
  const reference = encoded ?? url;
7097
7178
  const title = mimeType ? `${reference} \xB7 ${mimeType}` : reference;
7098
7179
  const href = viewableUrl(url);
@@ -7114,7 +7195,7 @@ function FileRef({
7114
7195
  "span",
7115
7196
  {
7116
7197
  title,
7117
- className: "min-w-0 truncate font-mono text-[12px] text-muted-foreground",
7198
+ className: "min-w-0 truncate font-mono text-[12px] text-muted-foreground max-sm:leading-11",
7118
7199
  children: label
7119
7200
  }
7120
7201
  ),
@@ -7221,12 +7302,12 @@ function frameableUrl(candidate) {
7221
7302
  if (/^https?:/.test(candidate.url)) return candidate.url;
7222
7303
  return candidate.fromResolver && candidate.url.startsWith("/") ? candidate.url : void 0;
7223
7304
  }
7305
+ function browserRenders(content, url) {
7306
+ return content.mimeType ? PREVIEWABLE_MIME_RE.test(content.mimeType) : PREVIEWABLE_EXT_RE.test(content.filename ?? url);
7307
+ }
7224
7308
  function previewableUrl(content, resolve) {
7225
7309
  const url = frameableUrl(paintable(content, resolve));
7226
- if (!url) return void 0;
7227
- const named = content.filename ?? url;
7228
- const renderable = content.mimeType ? PREVIEWABLE_MIME_RE.test(content.mimeType) : PREVIEWABLE_EXT_RE.test(named);
7229
- return renderable ? url : void 0;
7310
+ return url && browserRenders(content, url) ? url : void 0;
7230
7311
  }
7231
7312
  function isPdfDocument(content, url) {
7232
7313
  return content.mimeType ? /^application\/pdf\s*(;|$)/i.test(content.mimeType) : /\.pdf(\?|#|$)/i.test(content.filename ?? url);
@@ -7299,27 +7380,116 @@ function PdfUnavailable({
7299
7380
  ] })
7300
7381
  ] });
7301
7382
  }
7383
+ function ConvertedDocumentPreview({
7384
+ ask,
7385
+ name,
7386
+ file
7387
+ }) {
7388
+ const s = useFieldStrings();
7389
+ const { answer, retry } = useDocumentPdf(ask);
7390
+ const preparing = useRef(null);
7391
+ const retried = useRef(false);
7392
+ useEffect(() => {
7393
+ if (!retried.current) return;
7394
+ retried.current = false;
7395
+ preparing.current?.focus();
7396
+ });
7397
+ if (answer.state === "preparing") {
7398
+ return /* @__PURE__ */ jsxs(
7399
+ "div",
7400
+ {
7401
+ ref: preparing,
7402
+ role: "status",
7403
+ tabIndex: -1,
7404
+ className: cn(
7405
+ NESTED_FRAME,
7406
+ "flex items-center justify-center gap-2 rounded-lg border border-border bg-card/40 text-[12px] text-muted-foreground outline-none"
7407
+ ),
7408
+ children: [
7409
+ /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-4 animate-spin" }),
7410
+ /* @__PURE__ */ jsx("span", { children: s.documentPdfPreparing })
7411
+ ]
7412
+ }
7413
+ );
7414
+ }
7415
+ if (answer.state === "failed") {
7416
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card/40 px-3.5 py-3 sm:flex-row sm:items-center", children: [
7417
+ /* @__PURE__ */ jsxs(
7418
+ "p",
7419
+ {
7420
+ role: "alert",
7421
+ className: "flex min-w-0 flex-1 items-start gap-2 text-[12.5px] leading-relaxed text-muted-foreground",
7422
+ children: [
7423
+ /* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "mt-0.5 size-3.5 shrink-0" }),
7424
+ /* @__PURE__ */ jsx("span", { children: answer.message ?? s.documentPdfFailed })
7425
+ ]
7426
+ }
7427
+ ),
7428
+ /* @__PURE__ */ jsx(
7429
+ "button",
7430
+ {
7431
+ type: "button",
7432
+ onClick: () => {
7433
+ retried.current = true;
7434
+ retry();
7435
+ },
7436
+ className: cn(CARD_ACTION, "text-foreground", focusOutlineClass),
7437
+ children: s.tryAgain
7438
+ }
7439
+ )
7440
+ ] });
7441
+ }
7442
+ const gated = answer.state === "ready" ? viewableUrl(answer.url) : void 0;
7443
+ const url = gated && frameableUrl({ url: gated, fromResolver: true });
7444
+ if (url) return /* @__PURE__ */ jsx(DocumentPreview, { url, name, pdf: true, file, whole: false });
7445
+ return /* @__PURE__ */ jsx(
7446
+ "p",
7447
+ {
7448
+ role: "status",
7449
+ className: "rounded-lg border border-border bg-card/40 px-3.5 py-3 text-[12.5px] leading-relaxed text-muted-foreground",
7450
+ children: s.documentPdfUnavailable
7451
+ }
7452
+ );
7453
+ }
7302
7454
  function DocumentValue({ value, whole = false }) {
7303
7455
  const s = useFieldStrings();
7304
7456
  const resolve = useResolveUrl();
7457
+ const resolveDocumentPdf = useResolveDocumentPdf();
7305
7458
  const isApp = useFieldPresentation() === "app";
7306
7459
  const [open, setOpen] = useState(false);
7307
7460
  const file = useStuffFile("document", value);
7308
7461
  const content = readDocumentContent(value);
7309
7462
  if (!content) return /* @__PURE__ */ jsx(Absent, {});
7310
- const name = content.title ?? content.filename ?? fileLabel(content.url, content.filename, s);
7463
+ const label = fileLabel(content.url, content.filename, s);
7464
+ const title = content.title?.trim();
7465
+ const heading = title && title !== label ? title : void 0;
7466
+ const name = title || label;
7311
7467
  const preview = previewableUrl(content, resolve);
7312
7468
  const pdf = preview !== void 0 && isPdfDocument(content, preview);
7313
7469
  const opened = whole && isApp && pdf;
7470
+ const askPdf = preview === void 0 && !browserRenders(content, content.url) ? resolveDocumentPdf?.(content.url, {
7471
+ filename: content.filename,
7472
+ mimeType: content.mimeType
7473
+ }) : void 0;
7314
7474
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
7315
7475
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [
7316
- /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
7476
+ /* @__PURE__ */ jsx(
7477
+ FileText,
7478
+ {
7479
+ "aria-hidden": true,
7480
+ className: cn(
7481
+ "mt-0.5 size-4 shrink-0 text-muted-foreground",
7482
+ !heading && "max-sm:mt-3.5"
7483
+ )
7484
+ }
7485
+ ),
7317
7486
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [
7318
- name && /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] text-foreground", children: name }),
7487
+ heading && /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] text-foreground", children: heading }),
7319
7488
  /* @__PURE__ */ jsx(
7320
7489
  FileRef,
7321
7490
  {
7322
7491
  url: paintableUrl(content, resolve) ?? content.url,
7492
+ label,
7323
7493
  storageUrl: content.url,
7324
7494
  mimeType: content.mimeType,
7325
7495
  ...content.filename ? { filename: content.filename } : {},
@@ -7328,7 +7498,7 @@ function DocumentValue({ value, whole = false }) {
7328
7498
  ),
7329
7499
  content.snippet && /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-[12px] leading-relaxed text-muted-foreground", children: content.snippet })
7330
7500
  ] }),
7331
- preview && !opened && /* @__PURE__ */ jsxs(
7501
+ (preview || askPdf) && !opened && /* @__PURE__ */ jsxs(
7332
7502
  "button",
7333
7503
  {
7334
7504
  type: "button",
@@ -7345,7 +7515,8 @@ function DocumentValue({ value, whole = false }) {
7345
7515
  }
7346
7516
  )
7347
7517
  ] }),
7348
- preview && (opened || open) && /* @__PURE__ */ jsx(DocumentPreview, { url: preview, name, pdf, file, whole: opened })
7518
+ preview && (opened || open) && /* @__PURE__ */ jsx(DocumentPreview, { url: preview, name, pdf, file, whole: opened }),
7519
+ askPdf && open && /* @__PURE__ */ jsx(ConvertedDocumentPreview, { ask: askPdf, name, file }, content.url)
7349
7520
  ] });
7350
7521
  }
7351
7522
  var TILE_RENDITION_SIDE = 576;
@@ -8969,6 +9140,6 @@ function pdfFileName(field, value, baseName) {
8969
9140
  return name === void 0 ? "" : name.replace(/\.[^.]+$/, "");
8970
9141
  }
8971
9142
 
8972
- 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, actionShadowClass, checkedAccentClass, cn, enumLabeler, fieldControlClass, fieldLabel, fieldStringsFor, focusOutlineClass, headingClass, isThenable, linkTextClass, panelFlatShadowClass, panelRaisedShadowClass, panelSurfaceClass, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, selectedBorderClass, selectedFillClass, selectedFillStrongClass, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveRendition, useResolveShareUrl, useResolveUrl, useResolvedUrl };
8973
- //# sourceMappingURL=chunk-TD62R3P6.js.map
8974
- //# sourceMappingURL=chunk-TD62R3P6.js.map
9143
+ 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, actionShadowClass, checkedAccentClass, cn, enumLabeler, fieldControlClass, fieldLabel, fieldStringsFor, focusOutlineClass, headingClass, isThenable, linkTextClass, panelFlatShadowClass, panelRaisedShadowClass, panelSurfaceClass, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, selectedBorderClass, selectedFillClass, selectedFillStrongClass, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveDocumentPdf, useResolveRendition, useResolveShareUrl, useResolveUrl, useResolvedUrl };
9144
+ //# sourceMappingURL=chunk-TPHIENRU.js.map
9145
+ //# sourceMappingURL=chunk-TPHIENRU.js.map