@pipelex/mthds-form 0.4.0 → 0.6.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 (76) hide show
  1. package/README.md +15 -6
  2. package/dist/{chunk-6XD6UZSU.js → chunk-25I6JDIK.js} +2 -2
  3. package/dist/chunk-25I6JDIK.js.map +1 -0
  4. package/dist/chunk-2JWRG53S.js +33 -0
  5. package/dist/chunk-2JWRG53S.js.map +1 -0
  6. package/dist/chunk-45C2MKK6.js +94 -0
  7. package/dist/chunk-45C2MKK6.js.map +1 -0
  8. package/dist/chunk-4BSBBG2T.js +80 -0
  9. package/dist/chunk-4BSBBG2T.js.map +1 -0
  10. package/dist/{chunk-7BMOPXDY.js → chunk-GCXV76WU.js} +17 -9
  11. package/dist/chunk-GCXV76WU.js.map +1 -0
  12. package/dist/chunk-IIU3YK4O.js +16 -0
  13. package/dist/chunk-IIU3YK4O.js.map +1 -0
  14. package/dist/chunk-J4L32LD6.js +137 -0
  15. package/dist/chunk-J4L32LD6.js.map +1 -0
  16. package/dist/{chunk-KFZ5MZOM.js → chunk-LBFZXYCY.js} +3 -3
  17. package/dist/{chunk-KFZ5MZOM.js.map → chunk-LBFZXYCY.js.map} +1 -1
  18. package/dist/{chunk-O4O3QNVC.js → chunk-OD3VYXUY.js} +13 -3
  19. package/dist/chunk-OD3VYXUY.js.map +1 -0
  20. package/dist/{chunk-XEPD5VJQ.js → chunk-R7B7ID25.js} +8 -8
  21. package/dist/chunk-R7B7ID25.js.map +1 -0
  22. package/dist/{chunk-WXBSM2CE.js → chunk-T4NSAGOS.js} +8 -8
  23. package/dist/chunk-T4NSAGOS.js.map +1 -0
  24. package/dist/{chunk-HARYEWNG.js → chunk-XYQ7NXQZ.js} +2 -2
  25. package/dist/chunk-XYQ7NXQZ.js.map +1 -0
  26. package/dist/chunk-YEVVR6FE.js +66 -0
  27. package/dist/chunk-YEVVR6FE.js.map +1 -0
  28. package/dist/core/contracts.d.ts +27 -56
  29. package/dist/core/contracts.js +1 -1
  30. package/dist/core/derive.d.ts +79 -11
  31. package/dist/core/derive.js +4 -4
  32. package/dist/core/descriptor.d.ts +70 -26
  33. package/dist/core/descriptor.js +1 -1
  34. package/dist/core/file-formats.d.ts +102 -0
  35. package/dist/core/file-formats.js +3 -0
  36. package/dist/core/file-formats.js.map +1 -0
  37. package/dist/core/gate.d.ts +1 -1
  38. package/dist/core/gate.js +7 -8
  39. package/dist/core/gating-fields.d.ts +38 -0
  40. package/dist/core/gating-fields.js +6 -0
  41. package/dist/core/gating-fields.js.map +1 -0
  42. package/dist/core/index.d.ts +8 -3
  43. package/dist/core/index.js +14 -10
  44. package/dist/core/native-content.d.ts +219 -0
  45. package/dist/core/native-content.js +4 -0
  46. package/dist/core/{native-concepts.js.map → native-content.js.map} +1 -1
  47. package/dist/core/normalize-schema.js +2 -2
  48. package/dist/core/output-form.d.ts +45 -0
  49. package/dist/core/output-form.js +5 -0
  50. package/dist/core/output-form.js.map +1 -0
  51. package/dist/core/readiness.d.ts +2 -0
  52. package/dist/core/schema-utils.d.ts +13 -1
  53. package/dist/core/schema-utils.js +1 -1
  54. package/dist/core/stuff-files.d.ts +52 -0
  55. package/dist/core/stuff-files.js +5 -0
  56. package/dist/core/stuff-files.js.map +1 -0
  57. package/dist/core/values.d.ts +10 -3
  58. package/dist/core/values.js +7 -7
  59. package/dist/core/wire-format.js +3 -4
  60. package/dist/react/index.d.ts +278 -1
  61. package/dist/react/index.js +1378 -29
  62. package/dist/react/index.js.map +1 -1
  63. package/dist/styles.css +1 -1
  64. package/package.json +17 -3
  65. package/dist/chunk-6XD6UZSU.js.map +0 -1
  66. package/dist/chunk-7BMOPXDY.js.map +0 -1
  67. package/dist/chunk-HARYEWNG.js.map +0 -1
  68. package/dist/chunk-NMT7BRLO.js +0 -40
  69. package/dist/chunk-NMT7BRLO.js.map +0 -1
  70. package/dist/chunk-O4O3QNVC.js.map +0 -1
  71. package/dist/chunk-VMLXO5JS.js +0 -132
  72. package/dist/chunk-VMLXO5JS.js.map +0 -1
  73. package/dist/chunk-WXBSM2CE.js.map +0 -1
  74. package/dist/chunk-XEPD5VJQ.js.map +0 -1
  75. package/dist/core/native-concepts.d.ts +0 -78
  76. package/dist/core/native-concepts.js +0 -3
@@ -1,6 +1,9 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { RunField, BooleanRunField, DateRunField, EnumRunField, FileRunField, ListRunField, NumberRunField, ObjectRunField, ProseRunField, TextRunField, UnknownRunField, ConceptCategory } from '../core/descriptor.js';
4
+ import { HtmlContentView } from '../core/native-content.js';
5
+ import '../core/contracts.js';
6
+ import 'mthds/protocol';
4
7
 
5
8
  /**
6
9
  * Ambient state that any field in the tree may need, threaded unchanged through
@@ -202,6 +205,215 @@ interface UnknownFieldProps {
202
205
  */
203
206
  declare function UnknownField({ field, value, onChange, id, error, disabled }: UnknownFieldProps): react.JSX.Element;
204
207
 
208
+ interface ResultFieldProps {
209
+ field: RunField;
210
+ value: unknown;
211
+ /** Nesting depth, used only for indentation. */
212
+ depth?: number;
213
+ /**
214
+ * Suppress the field's own label. Set on a LIST ITEM, where the index already
215
+ * identifies the entry: the standard says an element descriptor's `name` is
216
+ * unused, and rendering it repeats the parent's label on every row.
217
+ */
218
+ hideLabel?: boolean;
219
+ }
220
+ /** The single dispatch point, mirroring `FieldRenderer` on the input side. */
221
+ declare function ResultField({ field, value, depth, hideLabel }: ResultFieldProps): react.JSX.Element | null;
222
+
223
+ /**
224
+ * A pipe's result, with the two views a result actually needs.
225
+ *
226
+ * ## Two, and why not three
227
+ *
228
+ * **Rendered** is the answer for a person: the descriptor-driven view, which
229
+ * knows a field is an enum, that a date arrived in the serializer's typed
230
+ * envelope, that fifteen records are a table. **JSON** is the answer for whoever
231
+ * is debugging the pipe: what exactly came back, verbatim, copyable. Those are
232
+ * different jobs and neither substitutes for the other.
233
+ *
234
+ * They are deliberately NOT peers, and the toggle should not read as a menu of
235
+ * equal options. One is the result; the other is the receipt. Rendered opens
236
+ * first, always.
237
+ *
238
+ * A third view is the one to resist. An engine-rendered HTML or plain-text
239
+ * presentation — the shape the runtime's own viewer offers — is a second human
240
+ * rendering of the same payload, produced by another codebase, carrying no
241
+ * descriptor (so it cannot know a kind, a plurality or a nesting) and unable to
242
+ * match a host's design system. It is a renderer that cannot be improved without
243
+ * shipping the engine. If a plain-text form is wanted, it is a COPY FORMAT and
244
+ * belongs behind a copy control, not beside the view that reads the standard.
245
+ */
246
+ type StuffViewerView = 'rendered' | 'json';
247
+ interface StuffViewerProps {
248
+ field: RunField;
249
+ value: unknown;
250
+ /** Which view opens first. Rendered, unless a host has a reason. */
251
+ defaultView?: StuffViewerView;
252
+ /**
253
+ * Names the saved files: `<baseName>.json`, and any file inside the stuff
254
+ * that carries no name of its own. Defaults to the field's name, which is
255
+ * what the header shows.
256
+ */
257
+ downloadBaseName?: string;
258
+ /**
259
+ * Hide the download control. For a host that saves results its own way, or a
260
+ * surface where saving makes no sense — a preview inside an editor, say.
261
+ */
262
+ hideDownload?: boolean;
263
+ className?: string;
264
+ }
265
+ /**
266
+ * The payload as JSON — the receipt, with the whole of it one click from the
267
+ * clipboard.
268
+ *
269
+ * Exported because it is also the honest FLOOR: a host that holds a value but
270
+ * not the artifacts describing it (an older engine, a spec restored without its
271
+ * validate report) should show the value and say why it is not laid out, rather
272
+ * than show nothing. That is a different thing from what `StuffViewer`'s JSON
273
+ * tab was — one labelled fallback, not one of three guesses offered as a
274
+ * choice.
275
+ */
276
+ declare function JsonView({ value }: {
277
+ value: unknown;
278
+ }): react.JSX.Element;
279
+ declare function StuffViewer({ field, value, defaultView, downloadBaseName, hideDownload, className, }: StuffViewerProps): react.JSX.Element;
280
+
281
+ /** How a host turns a stored reference into something fetchable. */
282
+ type ResolveForDownload = (url: string) => string | undefined;
283
+ /**
284
+ * Everything a stuff contains, saved.
285
+ *
286
+ * The rule the caller asked for, and the one that matches what a result IS: the
287
+ * data goes down as JSON, and any image or document inside it goes down as
288
+ * ITSELF. A JSON file holding a URL to a picture is not the picture, and a
289
+ * reader who clicks download on a report with three attachments wants the three
290
+ * attachments — the reference is what the payload had, not what they asked for.
291
+ *
292
+ * The JSON is written whenever the stuff is anything more than its files: a
293
+ * result that IS one image has nothing left over once the image is saved, so
294
+ * writing `{"url": …}` beside it would be a second file saying less than the
295
+ * first. A result with fields around its files keeps both.
296
+ *
297
+ * A file that cannot be fetched is opened in a tab instead of failing. The
298
+ * fetch is same-origin for a host that proxies its storage, but a remote URL
299
+ * with no CORS header cannot be read into a blob at all, and handing the reader
300
+ * the file in a tab is the difference between an awkward download and none.
301
+ */
302
+ declare function downloadStuff({ field, value, baseName, resolveUrl, }: {
303
+ field: RunField;
304
+ value: unknown;
305
+ /** Names the JSON file and prefixes any file that has no name of its own. */
306
+ baseName: string;
307
+ resolveUrl?: ResolveForDownload;
308
+ }): Promise<void>;
309
+
310
+ /**
311
+ * The host's seam for turning a stored reference into something a browser can
312
+ * fetch.
313
+ *
314
+ * ## Why a result view needs one at all
315
+ *
316
+ * A run's files do not come back as public URLs. They come back as
317
+ * `pipelex-storage://…` references, which are an identity in the runtime's
318
+ * store and resolve nowhere in a browser. Without a way to exchange them, every
319
+ * file arm in the result view degrades to naming the file — a PDF that cannot
320
+ * be previewed, an image gallery of blank tiles — and no amount of care in
321
+ * those arms can fix it, because the missing thing is a fact only the host has.
322
+ *
323
+ * ## Why it is SYNCHRONOUS
324
+ *
325
+ * The obvious shape is `(uri) => Promise<string | null>`, and it is the wrong
326
+ * one here. It would put a loading state, a race and an effect into every arm
327
+ * that paints a file — inside table cells and gallery tiles, which are the
328
+ * places least able to carry them — and a gallery of twenty images would make
329
+ * twenty independent round trips as it scrolled.
330
+ *
331
+ * A host that resolves by REWRITING (the common case: map the reference onto
332
+ * its own `/assets` route, which checks ownership and streams the object) has
333
+ * no round trip to make and is served exactly by a pure function. A host that
334
+ * genuinely must presign is better off resolving the run's references in one
335
+ * batch — it holds the payload before it renders it — and closing over the
336
+ * result, which is also the only shape that does not make a gallery quadratic.
337
+ *
338
+ * So the seam is a pure function, and the async case is a lookup in a map the
339
+ * host filled. That is a real constraint on a host and it is stated rather than
340
+ * hidden.
341
+ *
342
+ * ## The contract
343
+ *
344
+ * Return `undefined` for a reference this host cannot resolve, and the view
345
+ * falls back to naming the file — which is what it does with no resolver at
346
+ * all. Never return a URL that will 404: a broken image tile says less than a
347
+ * filename.
348
+ */
349
+ type ResolveUrl = (url: string) => string | undefined;
350
+ /**
351
+ * How a stored reference becomes a URL that works OUTSIDE this page — pasted
352
+ * into another tab, another app, a colleague's message.
353
+ *
354
+ * Separate from `resolveUrl`, because the two answer different questions and a
355
+ * host's answers genuinely differ. A display URL may be a path on the host's
356
+ * own origin behind its session (`/api/assets/…`), which is what lets a strict
357
+ * `img-src 'self'` policy stand and what keeps a picture from expiring while
358
+ * the tab is open. That URL is useless on the clipboard: whoever pastes it has
359
+ * no session. A share URL therefore carries its own credential — a freshly
360
+ * minted presigned URL — and is minted per click rather than held, because it
361
+ * starts expiring the moment it exists.
362
+ *
363
+ * Async for the same reason: minting one is a round trip. Omit it and the copy
364
+ * control falls back to the display URL, which is right for a host whose
365
+ * storage is public anyway.
366
+ */
367
+ type ResolveShareUrl = (url: string) => Promise<string | undefined>;
368
+ interface ResultEnv {
369
+ resolveUrl?: ResolveUrl;
370
+ resolveShareUrl?: ResolveShareUrl;
371
+ }
372
+ declare function ResultEnvProvider({ resolveUrl, resolveShareUrl, children, }: ResultEnv & {
373
+ children: ReactNode;
374
+ }): react.JSX.Element;
375
+ /**
376
+ * The resolver, already applied.
377
+ *
378
+ * Every file arm calls this rather than reading the context and remembering to
379
+ * apply it: `useResolvedUrl(url)` is the URL to actually use, and a component
380
+ * that forgets to call it is the bug this shape prevents. An unresolvable
381
+ * reference comes back as the reference, so the arms' existing
382
+ * `isViewableUrl` check still decides whether it can be linked or only named.
383
+ */
384
+ declare function useResolvedUrl(url: string): string;
385
+ /** For the non-React readers that need the same answer (previewability). */
386
+ declare function useResolveUrl(): ResolveUrl | undefined;
387
+ /** The share-URL minter, when the host supplies one. */
388
+ declare function useResolveShareUrl(): ResolveShareUrl | undefined;
389
+
390
+ interface HtmlPreviewProps {
391
+ content: HtmlContentView;
392
+ /**
393
+ * Beyond this the frame scrolls rather than growing.
394
+ *
395
+ * Generous on purpose. A `native.Html` result is usually a whole report, and
396
+ * 480px showed three paragraphs of one with a scrollbar down the side while
397
+ * the panel around it sat empty — the reader scrolls twice to read once. The
398
+ * frame still sizes itself to its CONTENT, so a short snippet stays short;
399
+ * this only says how far it may grow before scrolling instead.
400
+ */
401
+ maxHeight?: number;
402
+ /**
403
+ * The frame's `img-src`. Defaults to `data: https:` — a method's HTML result
404
+ * embeds the images that method produced. Pass `"data:"` to block remote
405
+ * images entirely, or name an origin to allow only your own storage.
406
+ */
407
+ imgSrc?: string;
408
+ }
409
+ declare function HtmlPreview({ content, maxHeight, imgSrc, }: HtmlPreviewProps): react.JSX.Element;
410
+
411
+ interface MarkdownProps {
412
+ text: string;
413
+ className?: string;
414
+ }
415
+ declare function Markdown({ text, className }: MarkdownProps): react.JSX.Element;
416
+
205
417
  interface FieldShellProps {
206
418
  name: string;
207
419
  title?: string;
@@ -279,6 +491,53 @@ interface FieldStrings {
279
491
  preview: string;
280
492
  removeFileAria: string;
281
493
  previewUnavailablePdf: string;
494
+ /**
495
+ * What a result slot the run resolved as an absence SHOWS — a dash, not a
496
+ * sentence. Beside forty values in a grid, "not provided" repeated is louder
497
+ * than the data; a dash reads as the blank it is.
498
+ */
499
+ resultAbsent: string;
500
+ /**
501
+ * What that dash SAYS, to a screen reader.
502
+ *
503
+ * The two are separate because a glyph and a sentence are answers to different
504
+ * questions. A table column of hyphens read aloud is "hyphen, hyphen, hyphen",
505
+ * which is noise rather than information — so the dash is `aria-hidden` and
506
+ * this rides beside it, visually hidden.
507
+ */
508
+ resultAbsentDescription: string;
509
+ /**
510
+ * Shown above the raw value when a result cannot be laid out because the
511
+ * artifacts describing it are absent. It names the CAUSE — the descriptor,
512
+ * not the data — because the value is right there underneath and a reader
513
+ * who is not told why will assume the view is broken.
514
+ */
515
+ resultUndescribed: string;
516
+ /** The control that opens a result table row to show the rest of the record. */
517
+ toggleRowDetails: (index: number) => string;
518
+ /** The (visually hidden) header of the column those controls sit in. */
519
+ rowDetailsColumn: string;
520
+ /** The control that puts a file's whole URL on the clipboard. */
521
+ copyUrl: string;
522
+ /** The copy control beside any text value, markdown or not. */
523
+ copyText: string;
524
+ /** The download control's label. */
525
+ download: string;
526
+ /** Announced while the files are being fetched. */
527
+ downloading: string;
528
+ /** The result panel's two views, and the control that copies the payload. */
529
+ viewRendered: string;
530
+ viewJson: string;
531
+ copyJson: string;
532
+ resultViewGroup: string;
533
+ yes: string;
534
+ no: string;
535
+ /**
536
+ * Shown when a picked file is not a format this slot accepts. Takes the
537
+ * accept label so the message names what WOULD have worked - "not accepted"
538
+ * on its own leaves the user guessing at the list they just failed.
539
+ */
540
+ unsupportedFileType: (accept: string) => string;
282
541
  /** The optional-entries disclosure ("field" inside a concept, "input" at top level). */
283
542
  hideOptionalFields: string;
284
543
  hideOptionalInputs: string;
@@ -294,6 +553,24 @@ declare function FieldStringsProvider({ strings, children, }: {
294
553
  /** The strings the nearest provider supplies (English defaults when none does). */
295
554
  declare function useFieldStrings(): FieldStrings;
296
555
 
556
+ declare function FieldDomIdProvider({ prefix, children, }: {
557
+ /**
558
+ * Omit to mint one from `useId`; `''` writes the path unprefixed. An explicit
559
+ * prefix must be unique in the document - one provider, one form.
560
+ */
561
+ prefix?: string;
562
+ children: ReactNode;
563
+ }): react.JSX.Element;
564
+ /**
565
+ * The DOM id for a field path - the ONE place a path becomes an attribute.
566
+ *
567
+ * Called once per control so the `<label for>` and the control's `id` are the
568
+ * same string by construction: the pairing cannot drift, because there is only
569
+ * one call. `useId` runs unconditionally (hooks must) and is simply unused when
570
+ * a provider supplies the prefix.
571
+ */
572
+ declare function useFieldDomId(path: string): string;
573
+
297
574
  /**
298
575
  * How a field's LABEL chrome should read. The control itself never changes.
299
576
  *
@@ -324,4 +601,4 @@ declare function useFieldPresentation(): FieldPresentation;
324
601
  */
325
602
  declare function humanizeFieldName(name: string): string;
326
603
 
327
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, type FieldEnv, type FieldPresentation, FieldPresentationProvider, FieldRenderer, type FieldRendererProps, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, humanizeFieldName, useFieldPresentation, useFieldStrings };
604
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, type FieldEnv, type FieldPresentation, FieldPresentationProvider, FieldRenderer, type FieldRendererProps, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, HtmlPreview, type HtmlPreviewProps, ImageField, JsonView, ListField, Markdown, type MarkdownProps, NumberField, ObjectField, OptionalToggle, ProseField, type ResolveForDownload, type ResolveShareUrl, type ResolveUrl, ResultEnvProvider, ResultField, type ResultFieldProps, StuffViewer, type StuffViewerProps, type StuffViewerView, TextField, UnknownField, downloadStuff, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings, useResolveShareUrl, useResolveUrl, useResolvedUrl };