@pipelex/mthds-form 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +11 -0
  2. package/dist/{chunk-IR3OFSGD.js → chunk-2EGRONBV.js} +3 -3
  3. package/dist/{chunk-IR3OFSGD.js.map → chunk-2EGRONBV.js.map} +1 -1
  4. package/dist/{chunk-D2EQVWGN.js → chunk-7JRT6ZII.js} +1631 -171
  5. package/dist/chunk-7JRT6ZII.js.map +1 -0
  6. package/dist/{chunk-7KQXGG6J.js → chunk-ICUEHNEH.js} +19 -6
  7. package/dist/chunk-ICUEHNEH.js.map +1 -0
  8. package/dist/{chunk-DARLDVJM.js → chunk-JIGRWJTV.js} +2 -2
  9. package/dist/chunk-JIGRWJTV.js.map +1 -0
  10. package/dist/chunk-VH5CDQDR.js +41 -0
  11. package/dist/chunk-VH5CDQDR.js.map +1 -0
  12. package/dist/{chunk-G6SFMTL4.js → chunk-VT5E5W4E.js} +4 -3
  13. package/dist/chunk-VT5E5W4E.js.map +1 -0
  14. package/dist/{chunk-MJQ7CXUB.js → chunk-XO5OENNF.js} +25 -4
  15. package/dist/chunk-XO5OENNF.js.map +1 -0
  16. package/dist/core/gate.js +2 -2
  17. package/dist/core/index.d.ts +2 -1
  18. package/dist/core/index.js +6 -5
  19. package/dist/core/output-form.d.ts +5 -4
  20. package/dist/core/output-form.js +1 -1
  21. package/dist/core/readiness.d.ts +47 -2
  22. package/dist/core/readiness.js +1 -1
  23. package/dist/core/save-plan.d.ts +31 -1
  24. package/dist/core/save-plan.js +1 -1
  25. package/dist/core/seed.d.ts +56 -0
  26. package/dist/core/seed.js +5 -0
  27. package/dist/core/seed.js.map +1 -0
  28. package/dist/core/values.js +2 -2
  29. package/dist/generative/index.d.ts +2 -11
  30. package/dist/generative/index.js +11 -17
  31. package/dist/generative/index.js.map +1 -1
  32. package/dist/react/index.d.ts +699 -192
  33. package/dist/react/index.js +9 -3
  34. package/dist/styles.css +1 -1
  35. package/package.json +3 -1
  36. package/dist/chunk-7KQXGG6J.js.map +0 -1
  37. package/dist/chunk-D2EQVWGN.js.map +0 -1
  38. package/dist/chunk-DARLDVJM.js.map +0 -1
  39. package/dist/chunk-G6SFMTL4.js.map +0 -1
  40. package/dist/chunk-MJQ7CXUB.js.map +0 -1
@@ -3,11 +3,12 @@ export { a as FieldRenderer, b as FieldRendererProps } from '../field-renderer-C
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
5
  import { BooleanRunField, DateRunField, EnumRunField, FileRunField, ListRunField, NumberRunField, ObjectRunField, ProseRunField, TextRunField, UnknownRunField, RunField, ConceptCategory } from '../core/descriptor.js';
6
+ import { PipeInputFormDescriptor, PipeIOContract } from 'mthds/protocol';
7
+ import { Readiness } from '../core/readiness.js';
6
8
  import { SaveFiles, SaveFile, SaveResult } from '../core/save-plan.js';
7
9
  import { StuffFileKind } from '../core/stuff-files.js';
8
10
  import { HtmlContentView } from '../core/native-content.js';
9
11
  import '../core/contracts.js';
10
- import 'mthds/protocol';
11
12
  import '../core/file-formats.js';
12
13
 
13
14
  interface BooleanFieldProps {
@@ -164,6 +165,14 @@ interface ObjectFieldProps {
164
165
  * A structured concept: its sub-fields rendered inside one hairline-grouped
165
166
  * card so nesting stays legible. Empty optional sub-fields collapse behind a
166
167
  * "+ N optional" toggle - required and already-filled fields always show.
168
+ *
169
+ * A CLOSED optional sub-structure (absent value) folds behind that toggle and
170
+ * is counted like any empty optional field. The toggle also opens and closes
171
+ * them: expanding writes `seedObjectValue` into every closed one, collapsing
172
+ * writes `undefined` into every one, so they leave the payload. It reads
173
+ * expanded while any of them is open, so a restored value that holds one
174
+ * offers to close it. An open optional structure renders like any other: its
175
+ * label, description and card, with no badge.
167
176
  */
168
177
  declare function ObjectField({ field, value, onChange, id, error, env }: ObjectFieldProps): react.JSX.Element;
169
178
 
@@ -207,6 +216,381 @@ interface UnknownFieldProps {
207
216
  */
208
217
  declare function UnknownField({ field, value, onChange, id, error, disabled }: UnknownFieldProps): react.JSX.Element;
209
218
 
219
+ /**
220
+ * How a field's LABEL chrome, and the enum values a person reads, should read.
221
+ * What a control stores never changes.
222
+ *
223
+ * - `studio` (default): the field's name verbatim in mono, with its concept
224
+ * pill. The name IS the identifier a builder writes in the `.mthds` file, so
225
+ * showing it exactly - and typed - is the point. An enum value is shown as its
226
+ * code for the same reason: it has to match the bundle and the JSON the
227
+ * builder reads next.
228
+ * - `app`: the field's name as a human question in sans, no concept pill. Inside
229
+ * a method app, `full_name` and `native.Text` are implementation detail; the
230
+ * person filling the form has never seen the method's source. An enum value
231
+ * follows the label and reads as words.
232
+ *
233
+ * An enum value is the only value the two presentations show differently. A
234
+ * number, a text, a date and every other value render the same in both.
235
+ *
236
+ * Deliberately a context rather than a prop or a `FieldEnv` flag: only the
237
+ * components that own label chrome or an enum value's wording read it -
238
+ * `FieldShell`, `ObjectField`, `BooleanField`, `ListField`, `EnumField` and the
239
+ * result view - plus the file control, whose card hides a storage reference in
240
+ * `app`, and threading a prop would have meant editing every field control and
241
+ * both recursive containers.
242
+ */
243
+ type FieldPresentation = 'studio' | 'app';
244
+ declare function FieldPresentationProvider({ presentation, children, }: {
245
+ presentation: FieldPresentation;
246
+ children: ReactNode;
247
+ }): react.JSX.Element;
248
+ declare function useFieldPresentation(): FieldPresentation;
249
+ /**
250
+ * `full_name` → `Full name`. Only the first word is capitalised: these are
251
+ * labels, not titles, and Title Case on every word reads as a form from 2009.
252
+ * Takes IDENTIFIERS only - snake_case or kebab-case names, where a separator
253
+ * is structure. An authored `title` never goes through here: it is already
254
+ * human-facing, and its hyphens are spelling (`E-mail address`), not structure.
255
+ */
256
+ declare function humanizeFieldName(name: string): string;
257
+
258
+ interface FieldStrings {
259
+ /** Badge on an optional field's header row. */
260
+ optionalBadge: string;
261
+ selectPlaceholder: string;
262
+ typeValuePlaceholder: string;
263
+ writeHerePlaceholder: string;
264
+ /** Helper under the raw-JSON escape hatch. */
265
+ jsonHint: string;
266
+ decrease: string;
267
+ increase: string;
268
+ noItemsYet: string;
269
+ addItem: string;
270
+ removeItemAria: (index: number) => string;
271
+ itemsCount: (count: number) => string;
272
+ /** The same badge for a list the method gave an exact count (`Concept[N]`). */
273
+ itemsCountOf: (count: number, total: number) => string;
274
+ /** What a record's table cell says when none of its fields can name it. */
275
+ fieldsCount: (count: number) => string;
276
+ uploading: string;
277
+ dropToUpload: string;
278
+ dropOrBrowse: string;
279
+ pasteUrlInstead: string;
280
+ /**
281
+ * What a file field says in place of its dropzone when its host has no way to
282
+ * store a file, so a link is the only way in. The field's format hint follows
283
+ * it, because the link still has to point at a file the runtime can read.
284
+ */
285
+ uploadUnavailable: string;
286
+ /**
287
+ * The placeholder in the URL a person may paste instead of uploading. It names
288
+ * the web's own scheme only: a host whose runner also takes its own storage
289
+ * references can say so by overriding it, and no default shows an end user a
290
+ * platform's storage scheme.
291
+ */
292
+ urlPlaceholder: string;
293
+ /**
294
+ * The accessible name of that URL input, given the field's label as the form
295
+ * shows it. The field's label is bound to the file input, so without this the
296
+ * placeholder was the only thing a screen reader announced here.
297
+ *
298
+ * The label is EMPTY inside a list row: the list drops each row's label, and
299
+ * the row number it shows instead is a bare glyph tied to no control. So every
300
+ * row's input currently carries the same generic name, and giving rows
301
+ * distinct names is a separate piece of work on the list control.
302
+ */
303
+ fileUrlAria: (label: string) => string;
304
+ /**
305
+ * The title of an attached file's card when the value carries no filename.
306
+ * True of a pasted link as well as of an upload, which "Uploaded file" was not.
307
+ */
308
+ uploadedFile: string;
309
+ /**
310
+ * What names a file carried INSIDE its own URL (a `data:` URL), where a
311
+ * reference would otherwise be printed: its format and its decoded size. The
312
+ * string itself is forty thousand characters of base64 and says nothing.
313
+ */
314
+ encodedFileSummary: (format: string, bytes: number) => string;
315
+ preview: string;
316
+ removeFileAria: string;
317
+ previewUnavailablePdf: string;
318
+ /**
319
+ * What a result slot the run resolved as an absence SHOWS — a dash, not a
320
+ * sentence. Beside forty values in a grid, "not provided" repeated is louder
321
+ * than the data; a dash reads as the blank it is.
322
+ */
323
+ resultAbsent: string;
324
+ /**
325
+ * What that dash SAYS, to a screen reader.
326
+ *
327
+ * The two are separate because a glyph and a sentence are answers to different
328
+ * questions. A table column of hyphens read aloud is "hyphen, hyphen, hyphen",
329
+ * which is noise rather than information — so the dash is `aria-hidden` and
330
+ * this rides beside it, visually hidden.
331
+ */
332
+ resultAbsentDescription: string;
333
+ /**
334
+ * Shown above the raw value when a result cannot be laid out because the
335
+ * artifacts describing it are absent. It names the CAUSE — the descriptor,
336
+ * not the data — because the value is right there underneath and a reader
337
+ * who is not told why will assume the view is broken.
338
+ */
339
+ resultUndescribed: string;
340
+ /** The control that opens a result table row to show the rest of the record. */
341
+ toggleRowDetails: (index: number) => string;
342
+ /** The (visually hidden) header of the column those controls sit in. */
343
+ rowDetailsColumn: string;
344
+ /** The control that puts a file's whole URL on the clipboard. */
345
+ copyUrl: string;
346
+ /** The copy control beside any text value, markdown or not. */
347
+ copyText: string;
348
+ /** The download control's label. */
349
+ download: string;
350
+ /** Announced while the files are being fetched. */
351
+ downloading: string;
352
+ /**
353
+ * What the whole-result download says when some files did not arrive: the
354
+ * ones no URL could be found for, and the ones the delivery reported. Takes
355
+ * the names the files would have been saved under.
356
+ */
357
+ downloadIncomplete: (names: readonly string[]) => string;
358
+ /**
359
+ * The button beside each file that saves that one file, given the name it
360
+ * saves under: a gallery of buttons all called "Download" would leave a
361
+ * screen-reader user unable to tell which file each one saves.
362
+ */
363
+ downloadFile: (name: string) => string;
364
+ /** What that button says when the file did not arrive. */
365
+ downloadFileFailed: string;
366
+ /** The control on an HTML page that saves it as a PDF, in one click. */
367
+ downloadPdf: string;
368
+ /** What that control says while the PDF is being made. */
369
+ downloadPdfBusy: string;
370
+ /** What that control says when the PDF could not be made or saved. */
371
+ downloadPdfFailed: string;
372
+ /**
373
+ * Shown in place of an HTML page for the moment the stored pictures it names
374
+ * are being resolved through the host.
375
+ */
376
+ pageLoading: string;
377
+ /**
378
+ * The page's own HTML download, beside "Download PDF" on a result that is
379
+ * one page, where a bare "Download" would not say which of the two it saves.
380
+ */
381
+ downloadHtml: string;
382
+ /**
383
+ * The dialog that asks for a download's name, when the host turns
384
+ * `downloads.askFileName` on: its title, which also names the field, and its
385
+ * two buttons.
386
+ */
387
+ fileNameTitle: string;
388
+ fileNameCancel: string;
389
+ fileNameConfirm: string;
390
+ /** The result panel's two views, and the control that copies the payload. */
391
+ viewRendered: string;
392
+ viewJson: string;
393
+ copyJson: string;
394
+ resultViewGroup: string;
395
+ yes: string;
396
+ no: string;
397
+ /**
398
+ * Shown when a picked file is not a format this slot accepts. Takes the
399
+ * slot's own formats as the hint names them (`PNG, JPG`), after any narrowing
400
+ * a host applied, so the message names what WOULD have worked - "not
401
+ * accepted" on its own leaves the user guessing at the list they just failed.
402
+ */
403
+ unsupportedFileType: (formats: string) => string;
404
+ /** The optional-entries disclosure ("field" inside a concept, "input" at top level). */
405
+ hideOptionalFields: string;
406
+ hideOptionalInputs: string;
407
+ optionalFieldsCount: (count: number) => string;
408
+ optionalInputsCount: (count: number) => string;
409
+ /**
410
+ * The mark `MethodForm` puts on an input the run needs and does not have,
411
+ * once the person has tried to run.
412
+ */
413
+ requiredField: string;
414
+ /**
415
+ * The mark on an optional input that was started but is not complete, once
416
+ * the person has tried to run: a started structure owes its concept every
417
+ * required field.
418
+ */
419
+ incompleteField: string;
420
+ /** What a file field says when the host's `uploadFile` rejected its file. */
421
+ uploadFailed: string;
422
+ }
423
+ /** The English strings, which every control falls back to. Kept under this name for existing hosts. */
424
+ declare const DEFAULT_FIELD_STRINGS: FieldStrings;
425
+ /**
426
+ * Set the strings every control below speaks. `locale` picks a language pack by its code (`fr`,
427
+ * `fr-FR`, `en-GB`…; an unknown one falls back to English), and `strings` overrides single keys
428
+ * on top of it. Both are optional: with neither, the controls speak English.
429
+ */
430
+ declare function FieldStringsProvider({ locale, strings, children, }: {
431
+ locale?: string;
432
+ strings?: Partial<FieldStrings>;
433
+ children: ReactNode;
434
+ }): react.JSX.Element;
435
+ /** The strings the nearest provider supplies (English defaults when none does). */
436
+ declare function useFieldStrings(): FieldStrings;
437
+
438
+ /**
439
+ * A whole method's input form: every input of one pipe, built from its
440
+ * input-form descriptor and its contract, with the state a host would
441
+ * otherwise hand-write around `FieldRenderer` - the seed, the uploads, the
442
+ * top-level optional disclosure, the required marks and the run gate.
443
+ *
444
+ * It is two pieces, so the consumer keeps what is its own. `useMethodForm`
445
+ * holds the state and hands back the values, the run inputs and whether they
446
+ * are ready, wherever in the host's tree it is called - typically beside the
447
+ * run button. `MethodForm` renders the fields for that state and nothing else:
448
+ * no panel, no heading, no run button, no history, no `<form>` element. See
449
+ * docs/method-form.md.
450
+ */
451
+ /** The form's values, keyed by input name, in the controls' own shape. */
452
+ type MethodFormValues = Record<string, unknown>;
453
+ /** What a host's upload resolves to: the stored file's URL, or its URL and name. */
454
+ type UploadedFile = string | {
455
+ url: string;
456
+ filename?: string;
457
+ };
458
+ /** Where a dropped file goes: its field's id, which is its value path, split. */
459
+ interface MethodFormUploadContext {
460
+ /** The field's id, a dotted value path (`photos.1`, `invoice.source`). */
461
+ id: string;
462
+ /** The same path, split: what `setValueAtPath` takes. */
463
+ path: string[];
464
+ }
465
+ interface UseMethodFormOptions {
466
+ /** The pipe's input-form descriptor (`getPipeInputForm`). */
467
+ descriptor: PipeInputFormDescriptor;
468
+ /** The pipe's entry in `pipe_io_contracts` (`getPipeIOContract`). */
469
+ contract: PipeIOContract;
470
+ /**
471
+ * A transform over the derived fields, applied once before anything else
472
+ * reads them: narrowing a file slot (`narrowFileFormats`), dropping a child
473
+ * the host never sends. Pass a stable function (module-level or memoized):
474
+ * the fields are re-derived when it changes.
475
+ */
476
+ prepareFields?: (fields: RunField[]) => RunField[];
477
+ /**
478
+ * Input names to show first, in this order. Every input not named keeps its
479
+ * descriptor order after them; a name no input has is ignored.
480
+ */
481
+ order?: readonly string[];
482
+ /**
483
+ * What the form holds until its first edit. Absent or `null`, the method's
484
+ * own seed (`seedInputs`: the authored defaults, every optional structure
485
+ * closed). A function receives that seed and returns the values, for a host
486
+ * that lays something the method cannot know over it (today's date). Read on
487
+ * every render until the first edit, so it may change after hydration; from
488
+ * the first edit the form owns its values, and `reset` goes back to it.
489
+ */
490
+ initialValues?: MethodFormValues | ((seed: MethodFormValues) => MethodFormValues) | null;
491
+ /**
492
+ * Stores a dropped or picked file and resolves to what the field holds. Its
493
+ * presence is the capability: without it every file field is link-only (see
494
+ * docs/upload-seam.md). A rejection marks the field's upload as failed with
495
+ * `FieldStrings.uploadFailed`; the next file dropped there clears it. When
496
+ * it resolves to a bare URL, or omits the name, the file's own name is kept.
497
+ */
498
+ uploadFile?: (file: File, context: MethodFormUploadContext) => Promise<UploadedFile>;
499
+ }
500
+ /** What the form says to its consumer: the values, the run inputs, the gate. */
501
+ interface MethodFormState {
502
+ /** The fields the form renders, derived, prepared and ordered. */
503
+ fields: RunField[];
504
+ /** The current values, in the controls' shape. */
505
+ values: MethodFormValues;
506
+ /**
507
+ * The values as a run takes them: each input as `{ concept, content }`, an
508
+ * unfilled optional input left out, an empty plural input sent as `[]`
509
+ * (`apiInputsFromRunValues`). What a run call's `inputs` takes.
510
+ */
511
+ inputs: Record<string, unknown>;
512
+ /** How many of the inputs that gate a run are filled, and which are not. */
513
+ readiness: Readiness;
514
+ /** Whether a file upload is still in flight. */
515
+ uploading: boolean;
516
+ /** Whether a run may start: every gating input filled and no upload in flight. */
517
+ ready: boolean;
518
+ /** Whether `attempt` has been called since the last reset: required marks show. */
519
+ attempted: boolean;
520
+ }
521
+ /** The handle `useMethodForm` returns, and `MethodForm` renders. */
522
+ interface MethodFormController extends MethodFormState {
523
+ /** Replace the values, or update them from the previous ones. */
524
+ setValues: (next: MethodFormValues | ((previous: MethodFormValues) => MethodFormValues)) => void;
525
+ /**
526
+ * Back to `values` (a past run's inputs, say), or to the initial values when
527
+ * none is given. Forgets the edits, the attempt, the failed uploads and any
528
+ * upload still in flight, whose result is then dropped.
529
+ */
530
+ reset: (values?: MethodFormValues | null) => void;
531
+ /**
532
+ * The run button's press: marks the attempt, so every gating input still
533
+ * empty shows its required mark, and answers whether the run may start.
534
+ */
535
+ attempt: () => boolean;
536
+ /** Ids (value paths) with an upload in flight. Read by `MethodForm`. */
537
+ uploadingIds: ReadonlySet<string>;
538
+ /** Ids whose last upload failed. Read by `MethodForm`. */
539
+ failedUploadIds: ReadonlySet<string>;
540
+ /** The upload seam `MethodForm` hands its file fields, when the host uploads. */
541
+ dropFile: ((id: string, file: File) => void) | undefined;
542
+ }
543
+ /**
544
+ * The state of one method's input form. See `UseMethodFormOptions` for what it
545
+ * takes and `MethodFormController` for what it gives back; render it with
546
+ * `<MethodForm form={...} />`.
547
+ *
548
+ * A different method is a different form: remount (a new `key`) when the
549
+ * descriptor changes, as values belong to the inputs they were typed for.
550
+ */
551
+ declare function useMethodForm({ descriptor, contract, prepareFields, order, initialValues, uploadFile, }: UseMethodFormOptions): MethodFormController;
552
+ interface MethodFormProps {
553
+ /** The state to render, from `useMethodForm`. */
554
+ form: MethodFormController;
555
+ /** Holds every control read-only, as a host does while a run is in flight. */
556
+ disabled?: boolean;
557
+ /** `FieldEnv.allowUrl`: `false` takes the "paste a URL instead" link away. */
558
+ allowUrl?: boolean;
559
+ /** `FieldEnv.resolveUrl`: turns a stored reference into a URL a preview can load. */
560
+ resolveUrl?: (uri: string) => Promise<string | null>;
561
+ /**
562
+ * Whether the empty optional inputs fold behind a "+ N optional inputs"
563
+ * disclosure (the default), as they do inside a structure. `false` shows
564
+ * every input; a closed optional structure still folds, since the
565
+ * disclosure is what opens it.
566
+ */
567
+ foldOptional?: boolean;
568
+ /**
569
+ * A message per input name, shown on that input over the form's own marks:
570
+ * a server gate's verdict, say.
571
+ */
572
+ errors?: Readonly<Record<string, string>>;
573
+ /** Mounts `FieldStringsProvider` with this locale (`fr`, `fr-FR`…). */
574
+ locale?: string;
575
+ /** Mounts `FieldStringsProvider` with these keys over the locale's pack. */
576
+ strings?: Partial<FieldStrings>;
577
+ /** Mounts `FieldPresentationProvider`; absent, the nearest one applies. */
578
+ presentation?: FieldPresentation;
579
+ /** Classes for the stack the fields sit in. */
580
+ className?: string;
581
+ }
582
+ /**
583
+ * Every input of the method `form` holds, rendered through `FieldRenderer`.
584
+ *
585
+ * Each input's id is its name, so a file's upload id is its value path. A
586
+ * gating input still empty after `form.attempt()` is marked `requiredField`,
587
+ * and an optional input started but not complete `incompleteField`; a host's
588
+ * `errors` entry replaces either. The top-level "+ N optional inputs"
589
+ * disclosure folds and opens exactly as the one inside a structure does,
590
+ * optional structures included.
591
+ */
592
+ declare function MethodForm({ locale, strings, presentation, ...props }: MethodFormProps): react.ReactElement<unknown, string | react.JSXElementConstructor<any>>;
593
+
210
594
  interface ResultFieldProps {
211
595
  field: RunField;
212
596
  value: unknown;
@@ -228,6 +612,71 @@ interface ResultFieldProps {
228
612
  */
229
613
  declare function ResultField(props: ResultFieldProps): react.JSX.Element;
230
614
 
615
+ /**
616
+ * The `pipelex-storage://` references inside an HTML page, exchanged for URLs a
617
+ * browser can load before the page is framed, rasterised or saved.
618
+ *
619
+ * A method that renders a page (a quote, an invoice) embeds the pictures it
620
+ * produced as `<img src="pipelex-storage://…">`. The file arms of the result
621
+ * view hand such a reference to the host's resolver, but a page is markup, and
622
+ * nothing inside it went through that seam: the frame's policy refuses the
623
+ * scheme, so every picture showed as a broken image, and a host had to rewrite
624
+ * the markup itself before handing it over. Here it is done once, for every
625
+ * place a page goes: the preview, the PDF and the HTML download.
626
+ *
627
+ * ## What is read, and how
628
+ *
629
+ * The markup is parsed with `DOMParser`, never searched with a pattern, because
630
+ * an attribute value a regex finds may be inside a comment, a `<textarea>` or
631
+ * another attribute, and an `<img>` it misses may be spelled with single
632
+ * quotes, no quotes or an uppercase tag. A `DOMParser` document is inert: no
633
+ * script runs and nothing is fetched while it is read. A whole document is
634
+ * parsed as one; a fragment is parsed as a `<template>`'s content, which keeps
635
+ * a leading `<style>` or `<meta>` where it was rather than moving it into a
636
+ * head the fragment never had.
637
+ *
638
+ * The references read are the ones a page fetches as a picture: `src` and
639
+ * `srcset` on `<img>` and `<source>`, a `<video>`'s `poster`, the legacy
640
+ * `background` attribute, an SVG `<image>`'s `href`, and every CSS `url()` in a
641
+ * `style` attribute or a `<style>` element (inside CSS, which is text, the
642
+ * `url()` tokens are found with a pattern; the HTML around them never is).
643
+ *
644
+ * ## How each reference is answered
645
+ *
646
+ * Every distinct reference is asked for once. When the host gave a bulk
647
+ * resolver (`resolveUrls`), all of them go in one call, which is one round trip
648
+ * for a page with twenty pictures; its answer is then checked by the URL gate
649
+ * (`viewableUrl`), as every resolver's answer is. A reference the bulk answer
650
+ * left out, or answered with something the gate refuses, is asked of the
651
+ * per-reference resolver (`resolveUrl`) next. A reference neither answers is
652
+ * left as it was: the picture stays broken, and the page still shows.
653
+ *
654
+ * A bulk resolver that rejects, throws or takes longer than `timeoutMs` is
655
+ * read as having answered nothing, so a page never waits on it for good.
656
+ *
657
+ * ## What comes back
658
+ *
659
+ * The markup with each answered reference replaced, serialised from the parsed
660
+ * tree. Markup holding no reference that changed comes back as the very same
661
+ * string, so a page with no stored pictures is never re-serialised.
662
+ */
663
+ /** Answers a batch of stored references; see `ResultEnvProvider`'s `resolveUrls`. */
664
+ type ResolveUrls = (uris: readonly string[]) => Promise<ReadonlyMap<string, string | null | undefined> | Readonly<Record<string, string | null | undefined>>>;
665
+ interface MarkupResolvers {
666
+ /** One reference at a time, synchronously, as the result view's file arms ask. */
667
+ resolveUrl?: (url: string) => string | undefined;
668
+ /** Every reference of the page in one call. Asked first when present. */
669
+ resolveUrls?: ResolveUrls;
670
+ }
671
+ /**
672
+ * The markup with every stored reference its pictures name resolved through
673
+ * the host's resolvers; see the module comment for the order they are asked in.
674
+ * Never rejects, and never waits on a bulk resolver past `timeoutMs`.
675
+ */
676
+ declare function resolveHtmlStorageUrls(markup: string, { resolveUrl, resolveUrls }: MarkupResolvers, { timeoutMs }?: {
677
+ timeoutMs?: number;
678
+ }): Promise<string>;
679
+
231
680
  /**
232
681
  * The host's seam for turning a stored reference into something a browser can
233
682
  * fetch.
@@ -329,9 +778,47 @@ interface DownloadDisplay {
329
778
  * `'document'`, and `'markup'` for an HTML page.
330
779
  */
331
780
  files?: boolean | readonly StuffFileKind[];
781
+ /**
782
+ * The "Download PDF" control on each HTML page, which makes a PDF of the
783
+ * page in the browser and saves it. Drawn unless `false`.
784
+ */
785
+ pdf?: boolean;
786
+ /**
787
+ * Ask the reader for the file's name before each download, in a dialog
788
+ * pre-filled with the name the plan chose. Off unless `true`.
789
+ *
790
+ * It is a behaviour of every download control rather than a control of its
791
+ * own, which is why it sits beside the three that say which are drawn: the
792
+ * PDF, the page's HTML, a file's own button, and the whole-result download.
793
+ * A whole-result download saving one file asks for that file's name; one
794
+ * saving several asks for the name they share, the base name, which names
795
+ * the JSON copy and every file the payload did not name itself.
796
+ */
797
+ askFileName?: boolean;
332
798
  }
333
799
  interface ResultEnv {
334
800
  resolveUrl?: ResolveUrl;
801
+ /**
802
+ * Every stored reference an HTML page's pictures name, resolved in one call.
803
+ *
804
+ * A `native.Html` page embeds the pictures its method produced as
805
+ * `pipelex-storage://` references (`<img src>`, `srcset`, a CSS `url()`),
806
+ * which the frame cannot load. Before the page is shown, made into a PDF or
807
+ * downloaded as HTML, the view collects the distinct references it holds and
808
+ * hands them here, once, and writes each URL this answers into the page.
809
+ *
810
+ * Async and BULK, unlike `resolveUrl`, because a page is the one place every
811
+ * reference is known at once, and a host that presigns can mint them all in
812
+ * one round trip. Answer a `Map` or a plain record from reference to URL;
813
+ * leave a reference out, or answer `null`, for one this host cannot resolve.
814
+ * Each answer passes the URL gate like any resolver's.
815
+ *
816
+ * Optional. Unset, or for a reference it leaves out, each reference is asked
817
+ * of `resolveUrl`; neither answering leaves that picture broken and the page
818
+ * shown. A call that rejects or takes longer than ten seconds is read as no
819
+ * answer, so a page is never held back by it for good.
820
+ */
821
+ resolveUrls?: ResolveUrls;
335
822
  resolveShareUrl?: ResolveShareUrl;
336
823
  proseImages?: ProseImages;
337
824
  /**
@@ -370,7 +857,7 @@ interface ResultEnv {
370
857
  */
371
858
  tableColumns?: number;
372
859
  }
373
- declare function ResultEnvProvider({ resolveUrl, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads, children, }: ResultEnv & {
860
+ declare function ResultEnvProvider({ resolveUrl, resolveUrls, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads, children, }: ResultEnv & {
374
861
  children: ReactNode;
375
862
  }): react.JSX.Element;
376
863
  /**
@@ -413,6 +900,23 @@ declare function useProseImages(): ProseImages;
413
900
  * match a host's design system. It is a renderer that cannot be improved without
414
901
  * shipping the engine. If a plain-text form is wanted, it is a COPY FORMAT and
415
902
  * belongs behind a copy control, not beside the view that reads the standard.
903
+ *
904
+ * ## Which views a reader is offered
905
+ *
906
+ * The JSON view is a builder's tool. In `studio` both views are offered; in
907
+ * `app` only the result is, so a person reading a method app's answer is not
908
+ * handed a switch to a receipt they have no use for. `views` overrides either
909
+ * default, and the switch is drawn only when it offers more than one view.
910
+ *
911
+ * ## A result that is one page
912
+ *
913
+ * A result whose node IS `native.Html`, or a concept refining it (read off the
914
+ * descriptor's `concept_ref` and `refines`, never off the value), is one page,
915
+ * and the panel shows it as that page: one row holding the title (none in
916
+ * `app`, where the page carries its own), "Download
917
+ * PDF" as the primary control and one HTML download, then the page edge to
918
+ * edge with no box around it. The page's own control row and the header's
919
+ * download saved the same file, so they are one control here.
416
920
  */
417
921
  type StuffViewerView = 'rendered' | 'json';
418
922
  interface StuffViewerProps {
@@ -438,7 +942,18 @@ interface StuffViewerProps {
438
942
  * is named after the thing they were reading rather than `output.json`.
439
943
  */
440
944
  name?: string;
441
- /** Which view opens first. Rendered, unless a host has a reason. */
945
+ /**
946
+ * Which views the reader may switch between, in the switch's order. Unset,
947
+ * it follows the presentation: both in `studio`, the rendered result alone in
948
+ * `app`, where the JSON receipt is a builder's tool. The switch is drawn only
949
+ * when more than one view is offered; an empty list counts as the rendered
950
+ * result alone.
951
+ */
952
+ views?: readonly StuffViewerView[];
953
+ /**
954
+ * Which view opens first. Rendered, unless a host has a reason. A view not
955
+ * among `views` opens the first one offered instead.
956
+ */
442
957
  defaultView?: StuffViewerView;
443
958
  /**
444
959
  * Names the saved files, whichever control saved them: the base name names
@@ -507,17 +1022,189 @@ interface HtmlPreviewProps {
507
1022
  * 480px showed three paragraphs of one with a scrollbar down the side while
508
1023
  * the panel around it sat empty — the reader scrolls twice to read once. The
509
1024
  * frame still sizes itself to its CONTENT, so a short snippet stays short;
510
- * this only says how far it may grow before scrolling instead.
1025
+ * this only says how far it may grow before scrolling instead. A whole
1026
+ * document has no limit unless one is passed: it is a page, and is read whole.
511
1027
  */
512
1028
  maxHeight?: number;
513
1029
  /**
514
- * The frame's `img-src`. Defaults to `data: https:` — a method's HTML result
515
- * embeds the images that method produced. Pass `"data:"` to block remote
516
- * images entirely, or name an origin to allow only your own storage.
1030
+ * The frame's `img-src`. Defaults to `'self' data: https:` — a method's HTML
1031
+ * result embeds the images that method produced. Pass `"data:"` to block
1032
+ * remote images entirely, or name an origin to allow only your own storage.
1033
+ */
1034
+ imgSrc?: string;
1035
+ /**
1036
+ * The frame's `font-src`. Follows `imgSrc` when unset: a page's fonts come
1037
+ * from where its images do, the host's own origin or a font service, and a
1038
+ * blocked one silently changes the face the page was designed in.
517
1039
  */
1040
+ fontSrc?: string;
1041
+ /**
1042
+ * Draw the "Download PDF" control above the page. Follows the result
1043
+ * environment's `downloads.pdf` when unset, which draws it. The result view
1044
+ * passes `false` and draws its own in the page's control row.
1045
+ */
1046
+ downloadPdf?: boolean;
1047
+ /** The PDF's file name, without the extension. `page` when unset. */
1048
+ pdfFileName?: string;
1049
+ /**
1050
+ * Draw a whole document with no border and no rounding, edge to edge with
1051
+ * its container: for a page that IS the view (a result that is one page),
1052
+ * where a box around it is chrome around chrome. A fragment keeps its themed
1053
+ * box either way, since that box is its padding.
1054
+ */
1055
+ bare?: boolean;
1056
+ /**
1057
+ * Let a whole document's frame shrink to the height its container leaves it
1058
+ * and scroll inside itself, when that container is a flex column of bounded
1059
+ * height (a host's scrolling panel, `flex min-h-0 flex-col overflow-y-auto`).
1060
+ * The page is then the one scroll container under the pointer, so the first
1061
+ * wheel gesture over it scrolls it. In any other container the frame stays
1062
+ * as tall as the page. The result view passes it for a result that is one
1063
+ * page; a fragment ignores it.
1064
+ */
1065
+ fill?: boolean;
1066
+ }
1067
+ declare function HtmlPreview({ content, maxHeight, imgSrc, fontSrc, downloadPdf, pdfFileName, bare, fill, }: HtmlPreviewProps): react.JSX.Element;
1068
+
1069
+ /**
1070
+ * A page's print stylesheet, applied on screen: what the PDF copy needs before
1071
+ * it is laid out and rasterised, since a browser offers no way to lay a frame
1072
+ * out as `print` media short of printing it.
1073
+ *
1074
+ * ## Promoting the print rules
1075
+ *
1076
+ * Every media query in the copy's stylesheets is rewritten to say what it would
1077
+ * say on paper: a query on `print` becomes one on `all`, a query on `screen` (or
1078
+ * any other media type) becomes `not all`, and `not` flips both, so `@media
1079
+ * print { … }` now applies and `@media screen { … }` no longer does, in the
1080
+ * order the author wrote them. A query with no media type (`@media (min-width:
1081
+ * 600px)`) is left alone: it is about the page's size, which the copy is laid
1082
+ * out at. A `<style media="print">` is rewritten the same way.
1083
+ *
1084
+ * Rewriting the queries in place, rather than unwrapping the rules out of their
1085
+ * blocks, keeps the cascade exactly as written: a print rule still comes after
1086
+ * the screen rules it overrides, and nested blocks keep their nesting.
1087
+ *
1088
+ * ## The page box
1089
+ *
1090
+ * `@page` says the sheet's size and margins. A rule with no selector is read,
1091
+ * the last one winning per property, as in the cascade; `:first`, `:left` and
1092
+ * named pages are not. A size is a keyword (`A4`, `letter`, optionally with
1093
+ * `landscape`) or two lengths; a margin is a length. What a page does not say is
1094
+ * A4 portrait with 1 cm margins on every side — what Chrome's own print uses
1095
+ * when a page names none, so a template laid out against Chrome's print lands
1096
+ * the same way.
1097
+ */
1098
+ /** A page's size and margins, in CSS pixels (96 to the inch). */
1099
+ interface PageBox {
1100
+ width: number;
1101
+ height: number;
1102
+ margin: {
1103
+ top: number;
1104
+ right: number;
1105
+ bottom: number;
1106
+ left: number;
1107
+ };
1108
+ }
1109
+
1110
+ /**
1111
+ * Make a PDF of an HTML page in the browser, with no server and no dialog: lay
1112
+ * a script-free copy of the page out as it would print, rasterise it, cut the
1113
+ * raster into pages at block boundaries, and wrap the pages in a PDF.
1114
+ *
1115
+ * ## The copy
1116
+ *
1117
+ * The page is laid out in a hidden frame of its own, never the preview on
1118
+ * screen, which carries the host's theme and is sized to the panel. The frame
1119
+ * document is the preview's (`html-frame.ts`), under the same policy: the same
1120
+ * `img-src` and `font-src`, no scripts. It is sandboxed `allow-same-origin` and
1121
+ * nothing else, so the host can read its layout and computed style, and the
1122
+ * markup still runs nothing: same-origin is dangerous only together with
1123
+ * scripts.
1124
+ *
1125
+ * Once loaded, every image is made eager and awaited and every face the page
1126
+ * declares is asked for and awaited, a face used only in print included; then
1127
+ * the page's print rules are promoted (`print-media.ts`), its `@page` box read,
1128
+ * and the frame resized to the page's drawable width and height, so the copy is
1129
+ * laid out as the print engine would lay it out on one sheet.
1130
+ *
1131
+ * ## The raster
1132
+ *
1133
+ * `modern-screenshot` clones the copy into an SVG `foreignObject`, inlining each
1134
+ * element's computed style and embedding the fonts and images it uses as data
1135
+ * URLs, which is what lets the browser paint it as an image. It is rendered
1136
+ * once for the flow, with the elements laid out `position: fixed` left out,
1137
+ * and once for those elements alone at one page's height, laid over every page
1138
+ * as a print engine repeats them. Each stretch of the flow a page draws is
1139
+ * painted from the SVG through its own `viewBox` at `scale` device pixels per
1140
+ * CSS pixel, so only one page is ever held as pixels, and text is rasterised
1141
+ * at print resolution rather than scaled up.
1142
+ *
1143
+ * ## The PDF
1144
+ *
1145
+ * Each page is encoded as a JPEG and placed full-bleed on a page of the size
1146
+ * the page box says, by `jspdf`. Both libraries are loaded with `import()` on
1147
+ * first use, so a host that never makes a PDF ships neither.
1148
+ */
1149
+ interface HtmlPdfOptions {
1150
+ /** The copy's `img-src`, as `HtmlPreview`'s. */
518
1151
  imgSrc?: string;
1152
+ /** The copy's `font-src`, as `HtmlPreview`'s. Follows `imgSrc` when unset. */
1153
+ fontSrc?: string;
1154
+ /** The typeface a fragment is laid out in, when its own CSS names none. */
1155
+ fontFamily?: string;
1156
+ /** The PDF's document title, in its metadata. */
1157
+ title?: string;
1158
+ /**
1159
+ * Device pixels per CSS pixel of the raster. 3 (288 dpi) when unset; 2 is
1160
+ * the least that prints crisp.
1161
+ */
1162
+ scale?: number;
1163
+ /** JPEG quality of each page, from 0 to 1. 0.92 when unset. */
1164
+ quality?: number;
519
1165
  }
520
- declare function HtmlPreview({ content, maxHeight, imgSrc, }: HtmlPreviewProps): react.JSX.Element;
1166
+ /** What `renderHtmlPdf` produced, for a caller that wants more than the bytes. */
1167
+ interface HtmlPdf {
1168
+ blob: Blob;
1169
+ pageCount: number;
1170
+ /** The page box the PDF was laid out on, in CSS pixels. */
1171
+ page: PageBox;
1172
+ }
1173
+ /**
1174
+ * Render `content` to a PDF. Rejects when the copy cannot be built or painted,
1175
+ * or when the libraries that paint and assemble it cannot be loaded.
1176
+ */
1177
+ declare function renderHtmlPdf(content: HtmlContentView, { imgSrc, fontSrc, fontFamily, title, scale, quality, }?: HtmlPdfOptions): Promise<HtmlPdf>;
1178
+
1179
+ interface PdfDownloadButtonProps {
1180
+ /** The page to make a PDF of. */
1181
+ content: HtmlContentView;
1182
+ /** The PDF's file name, without the extension. `page` when unset or empty. */
1183
+ fileName?: string;
1184
+ /** Where the page sits in the result, for the save plan (`path`). */
1185
+ path?: string;
1186
+ /** The PDF copy's `img-src`, as `HtmlPreview`'s. */
1187
+ imgSrc?: string;
1188
+ /** The PDF copy's `font-src`, as `HtmlPreview`'s. */
1189
+ fontSrc?: string;
1190
+ /** Draw it as the row's primary control: filled rather than outlined. */
1191
+ primary?: boolean;
1192
+ className?: string;
1193
+ }
1194
+ /**
1195
+ * "Download PDF" for an HTML page: one click, and the browser saves
1196
+ * `<fileName>.pdf`. The PDF is made in the browser (`renderHtmlPdf`) from a
1197
+ * script-free copy of the page laid out with its print rules, and handed to the
1198
+ * result environment's `saveFiles` like every other download, so a host that
1199
+ * delivers files its own way delivers this one too. The stored pictures the
1200
+ * page names are resolved through the environment's resolvers first, as its
1201
+ * preview's are.
1202
+ *
1203
+ * Busy while the PDF is made, which takes a moment for the fonts and the
1204
+ * raster; a PDF that could not be made or saved turns the control into a
1205
+ * warning, described to assistive technology, until the reader tries again.
1206
+ */
1207
+ declare function PdfDownloadButton({ content, fileName, path, imgSrc, fontSrc, primary, className, }: PdfDownloadButtonProps): react.JSX.Element;
521
1208
 
522
1209
  interface MarkdownProps {
523
1210
  text: string;
@@ -577,155 +1264,14 @@ interface OptionalToggleProps {
577
1264
  * The disclosure for empty optional entries. Optional inputs stay hidden until
578
1265
  * the user asks for them - required (and already-filled) entries always show, so
579
1266
  * the form opens at its simplest and grows only on demand. Used at the top level
580
- * and inside every structured concept, at any nesting depth.
1267
+ * and inside every structured concept, at any nesting depth. An optional
1268
+ * structure folds behind it too, and opens and closes with it: see
1269
+ * `openOptionalStructures` / `closeOptionalStructures` in the core.
581
1270
  */
582
1271
  declare function OptionalToggle({ count, expanded, onToggle, noun }: OptionalToggleProps): react.JSX.Element;
583
1272
 
584
1273
  declare const fieldControlClass: string;
585
1274
 
586
- interface FieldStrings {
587
- /** Badge on an optional field's header row. */
588
- optionalBadge: string;
589
- selectPlaceholder: string;
590
- typeValuePlaceholder: string;
591
- writeHerePlaceholder: string;
592
- /** Helper under the raw-JSON escape hatch. */
593
- jsonHint: string;
594
- decrease: string;
595
- increase: string;
596
- noItemsYet: string;
597
- addItem: string;
598
- removeItemAria: (index: number) => string;
599
- itemsCount: (count: number) => string;
600
- /** The same badge for a list the method gave an exact count (`Concept[N]`). */
601
- itemsCountOf: (count: number, total: number) => string;
602
- /** What a record's table cell says when none of its fields can name it. */
603
- fieldsCount: (count: number) => string;
604
- uploading: string;
605
- dropToUpload: string;
606
- dropOrBrowse: string;
607
- pasteUrlInstead: string;
608
- /**
609
- * What a file field says in place of its dropzone when its host has no way to
610
- * store a file, so a link is the only way in. The field's format hint follows
611
- * it, because the link still has to point at a file the runtime can read.
612
- */
613
- uploadUnavailable: string;
614
- /**
615
- * The placeholder in the URL a person may paste instead of uploading. It names
616
- * the web's own scheme only: a host whose runner also takes its own storage
617
- * references can say so by overriding it, and no default shows an end user a
618
- * platform's storage scheme.
619
- */
620
- urlPlaceholder: string;
621
- /**
622
- * The accessible name of that URL input, given the field's label as the form
623
- * shows it. The field's label is bound to the file input, so without this the
624
- * placeholder was the only thing a screen reader announced here.
625
- *
626
- * The label is EMPTY inside a list row: the list drops each row's label, and
627
- * the row number it shows instead is a bare glyph tied to no control. So every
628
- * row's input currently carries the same generic name, and giving rows
629
- * distinct names is a separate piece of work on the list control.
630
- */
631
- fileUrlAria: (label: string) => string;
632
- /**
633
- * The title of an attached file's card when the value carries no filename.
634
- * True of a pasted link as well as of an upload, which "Uploaded file" was not.
635
- */
636
- uploadedFile: string;
637
- /**
638
- * What names a file carried INSIDE its own URL (a `data:` URL), where a
639
- * reference would otherwise be printed: its format and its decoded size. The
640
- * string itself is forty thousand characters of base64 and says nothing.
641
- */
642
- encodedFileSummary: (format: string, bytes: number) => string;
643
- preview: string;
644
- removeFileAria: string;
645
- previewUnavailablePdf: string;
646
- /**
647
- * What a result slot the run resolved as an absence SHOWS — a dash, not a
648
- * sentence. Beside forty values in a grid, "not provided" repeated is louder
649
- * than the data; a dash reads as the blank it is.
650
- */
651
- resultAbsent: string;
652
- /**
653
- * What that dash SAYS, to a screen reader.
654
- *
655
- * The two are separate because a glyph and a sentence are answers to different
656
- * questions. A table column of hyphens read aloud is "hyphen, hyphen, hyphen",
657
- * which is noise rather than information — so the dash is `aria-hidden` and
658
- * this rides beside it, visually hidden.
659
- */
660
- resultAbsentDescription: string;
661
- /**
662
- * Shown above the raw value when a result cannot be laid out because the
663
- * artifacts describing it are absent. It names the CAUSE — the descriptor,
664
- * not the data — because the value is right there underneath and a reader
665
- * who is not told why will assume the view is broken.
666
- */
667
- resultUndescribed: string;
668
- /** The control that opens a result table row to show the rest of the record. */
669
- toggleRowDetails: (index: number) => string;
670
- /** The (visually hidden) header of the column those controls sit in. */
671
- rowDetailsColumn: string;
672
- /** The control that puts a file's whole URL on the clipboard. */
673
- copyUrl: string;
674
- /** The copy control beside any text value, markdown or not. */
675
- copyText: string;
676
- /** The download control's label. */
677
- download: string;
678
- /** Announced while the files are being fetched. */
679
- downloading: string;
680
- /**
681
- * What the whole-result download says when some files did not arrive: the
682
- * ones no URL could be found for, and the ones the delivery reported. Takes
683
- * the names the files would have been saved under.
684
- */
685
- downloadIncomplete: (names: readonly string[]) => string;
686
- /**
687
- * The button beside each file that saves that one file, given the name it
688
- * saves under: a gallery of buttons all called "Download" would leave a
689
- * screen-reader user unable to tell which file each one saves.
690
- */
691
- downloadFile: (name: string) => string;
692
- /** What that button says when the file did not arrive. */
693
- downloadFileFailed: string;
694
- /** The result panel's two views, and the control that copies the payload. */
695
- viewRendered: string;
696
- viewJson: string;
697
- copyJson: string;
698
- resultViewGroup: string;
699
- yes: string;
700
- no: string;
701
- /**
702
- * Shown when a picked file is not a format this slot accepts. Takes the
703
- * slot's own formats as the hint names them (`PNG, JPG`), after any narrowing
704
- * a host applied, so the message names what WOULD have worked - "not
705
- * accepted" on its own leaves the user guessing at the list they just failed.
706
- */
707
- unsupportedFileType: (formats: string) => string;
708
- /** The optional-entries disclosure ("field" inside a concept, "input" at top level). */
709
- hideOptionalFields: string;
710
- hideOptionalInputs: string;
711
- optionalFieldsCount: (count: number) => string;
712
- optionalInputsCount: (count: number) => string;
713
- }
714
- /** The English strings, which every control falls back to. Kept under this name for existing hosts. */
715
- declare const DEFAULT_FIELD_STRINGS: FieldStrings;
716
- /**
717
- * Set the strings every control below speaks. `locale` picks a language pack by its code (`fr`,
718
- * `fr-FR`, `en-GB`…; an unknown one falls back to English), and `strings` overrides single keys
719
- * on top of it. Both are optional: with neither, the controls speak English.
720
- */
721
- declare function FieldStringsProvider({ locale, strings, children, }: {
722
- locale?: string;
723
- strings?: Partial<FieldStrings>;
724
- children: ReactNode;
725
- }): react.JSX.Element;
726
- /** The strings the nearest provider supplies (English defaults when none does). */
727
- declare function useFieldStrings(): FieldStrings;
728
-
729
1275
  /**
730
1276
  * The controls' strings in English: the package's defaults, and the pack every unknown or missing
731
1277
  * locale falls back to (see `fieldStringsFor` in `./index.ts`).
@@ -785,43 +1331,4 @@ declare function FieldDomIdProvider({ prefix, children, }: {
785
1331
  */
786
1332
  declare function useFieldDomId(path: string): string;
787
1333
 
788
- /**
789
- * How a field's LABEL chrome, and the enum values a person reads, should read.
790
- * What a control stores never changes.
791
- *
792
- * - `studio` (default): the field's name verbatim in mono, with its concept
793
- * pill. The name IS the identifier a builder writes in the `.mthds` file, so
794
- * showing it exactly - and typed - is the point. An enum value is shown as its
795
- * code for the same reason: it has to match the bundle and the JSON the
796
- * builder reads next.
797
- * - `app`: the field's name as a human question in sans, no concept pill. Inside
798
- * a method app, `full_name` and `native.Text` are implementation detail; the
799
- * person filling the form has never seen the method's source. An enum value
800
- * follows the label and reads as words.
801
- *
802
- * An enum value is the only value the two presentations show differently. A
803
- * number, a text, a date and every other value render the same in both.
804
- *
805
- * Deliberately a context rather than a prop or a `FieldEnv` flag: only the
806
- * components that own label chrome or an enum value's wording read it -
807
- * `FieldShell`, `ObjectField`, `BooleanField`, `ListField`, `EnumField` and the
808
- * result view - plus the file control, whose card hides a storage reference in
809
- * `app`, and threading a prop would have meant editing every field control and
810
- * both recursive containers.
811
- */
812
- type FieldPresentation = 'studio' | 'app';
813
- declare function FieldPresentationProvider({ presentation, children, }: {
814
- presentation: FieldPresentation;
815
- children: ReactNode;
816
- }): react.JSX.Element;
817
- declare function useFieldPresentation(): FieldPresentation;
818
- /**
819
- * `full_name` → `Full name`. Only the first word is capitalised: these are
820
- * labels, not titles, and Title Case on every word reads as a form from 2009.
821
- * Takes IDENTIFIERS only - snake_case or kebab-case names, where a separator
822
- * is structure. An authored `title` never goes through here: it is already
823
- * human-facing, and its hyphens are spelling (`E-mail address`), not structure.
824
- */
825
- declare function humanizeFieldName(name: string): string;
826
-
827
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, type DownloadDisplay, EN_FIELD_STRINGS, EnumField, FIELD_STRINGS_BY_LOCALE, FR_FIELD_STRINGS, FieldDomIdProvider, FieldEnv, type FieldPresentation, FieldPresentationProvider, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, HtmlPreview, type HtmlPreviewProps, ImageField, JsonView, ListField, Markdown, type MarkdownProps, NumberField, ObjectField, OptionalToggle, ProseField, type ProseImages, type ResolveShareUrl, type ResolveUrl, ResultEnvProvider, ResultField, type ResultFieldProps, SUPPORTED_LOCALES, StuffViewer, type StuffViewerProps, type StuffViewerView, TextField, UnknownField, fieldControlClass, fieldStringsFor, humanizeFieldName, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
1334
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, type DownloadDisplay, EN_FIELD_STRINGS, EnumField, FIELD_STRINGS_BY_LOCALE, FR_FIELD_STRINGS, FieldDomIdProvider, FieldEnv, type FieldPresentation, FieldPresentationProvider, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, type HtmlPdf, type HtmlPdfOptions, HtmlPreview, type HtmlPreviewProps, ImageField, JsonView, ListField, Markdown, type MarkdownProps, type MarkupResolvers, MethodForm, type MethodFormController, type MethodFormProps, type MethodFormState, type MethodFormUploadContext, type MethodFormValues, NumberField, ObjectField, OptionalToggle, PdfDownloadButton, type PdfDownloadButtonProps, ProseField, type ProseImages, type ResolveShareUrl, type ResolveUrl, type ResolveUrls, ResultEnvProvider, ResultField, type ResultFieldProps, SUPPORTED_LOCALES, StuffViewer, type StuffViewerProps, type StuffViewerView, TextField, UnknownField, type UploadedFile, type UseMethodFormOptions, fieldControlClass, fieldStringsFor, humanizeFieldName, renderHtmlPdf, resolveHtmlStorageUrls, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useMethodForm, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };