@pipelex/mthds-form 0.1.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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The `RunField` descriptor - the consumer-facing currency of the form kernel.
3
+ *
4
+ * A method's inputs arrive from the API as a map of variable name →
5
+ * `PipeInputContract` ({ concept_ref, json_schema }). That JSON Schema is rich
6
+ * (Pydantic-generated) and not pleasant to render directly. `buildRunFields`
7
+ * (in `derive.ts`) normalizes it once into a flat, recursive `RunField[]`
8
+ * descriptor that the presentational field components consume - so the
9
+ * components never touch raw JSON Schema, and stories can describe any input
10
+ * shape declaratively.
11
+ *
12
+ * Pure module: no React, no API calls. Trivially unit-testable.
13
+ */
14
+ type RunFieldKind = 'text' | 'prose' | 'date' | 'number' | 'boolean' | 'enum' | 'document' | 'image' | 'object' | 'list' | 'unknown';
15
+ interface RunFieldCommon {
16
+ /** The variable identifier as written in the method (rendered in mono). */
17
+ name: string;
18
+ /** Human label; falls back to `name` when absent. */
19
+ title?: string;
20
+ /** The concept this input carries, e.g. `native.Document`, `demo.Invoice`. */
21
+ conceptRef?: string;
22
+ /** One-line helper text shown under the label. */
23
+ description?: string;
24
+ /** Required inputs always show; optional ones can collapse. */
25
+ required: boolean;
26
+ /**
27
+ * Whether Run waits for this input - set on TOP-LEVEL fields only, straight
28
+ * from `inputMustBeFilled` (see `mustBeFilled` in `readiness.ts` for why this
29
+ * is not the same question as `required`). Nested fields leave it undefined;
30
+ * they are gated through their parent's `fieldFilled`.
31
+ */
32
+ gating?: boolean;
33
+ }
34
+ interface TextRunField extends RunFieldCommon {
35
+ kind: 'text';
36
+ placeholder?: string;
37
+ }
38
+ interface ProseRunField extends RunFieldCommon {
39
+ kind: 'prose';
40
+ placeholder?: string;
41
+ }
42
+ interface DateRunField extends RunFieldCommon {
43
+ kind: 'date';
44
+ /**
45
+ * True when the backing schema is `format: date-time`: the stored value is a
46
+ * full RFC 3339 timestamp (`2026-07-06T00:00:00Z`) so it passes the API's
47
+ * `date-time` validation, even though the control only asks for a day. False
48
+ * for a bare `format: date`, where the value stays `YYYY-MM-DD`.
49
+ */
50
+ datetime: boolean;
51
+ }
52
+ interface NumberRunField extends RunFieldCommon {
53
+ kind: 'number';
54
+ integer: boolean;
55
+ min?: number;
56
+ max?: number;
57
+ }
58
+ interface BooleanRunField extends RunFieldCommon {
59
+ kind: 'boolean';
60
+ }
61
+ interface EnumRunField extends RunFieldCommon {
62
+ kind: 'enum';
63
+ options: string[];
64
+ }
65
+ interface FileRunField extends RunFieldCommon {
66
+ kind: 'document' | 'image';
67
+ /** Accept hint shown to the user, e.g. "PDF, PNG, JPG". */
68
+ accept?: string;
69
+ }
70
+ interface ObjectRunField extends RunFieldCommon {
71
+ kind: 'object';
72
+ fields: RunField[];
73
+ }
74
+ interface ListRunField extends RunFieldCommon {
75
+ kind: 'list';
76
+ /** The element descriptor (its `name` is unused; the index labels items). */
77
+ item: RunField;
78
+ }
79
+ interface UnknownRunField extends RunFieldCommon {
80
+ kind: 'unknown';
81
+ }
82
+ type RunField = TextRunField | ProseRunField | DateRunField | NumberRunField | BooleanRunField | EnumRunField | FileRunField | ObjectRunField | ListRunField | UnknownRunField;
83
+ /** The short, friendly category label for a concept_ref (drives the pill dot). */
84
+ type ConceptCategory = 'text' | 'date' | 'document' | 'image' | 'number' | 'boolean' | 'choice' | 'structured' | 'list';
85
+ declare function conceptCategory(field: RunField): ConceptCategory;
86
+
87
+ export { type BooleanRunField as B, type ConceptCategory as C, type DateRunField as D, type EnumRunField as E, type FileRunField as F, type ListRunField as L, type NumberRunField as N, type ObjectRunField as O, type ProseRunField as P, type RunField as R, type TextRunField as T, type UnknownRunField as U, type RunFieldCommon as a, type RunFieldKind as b, conceptCategory as c };
@@ -0,0 +1,276 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { R as RunField, B as BooleanRunField, D as DateRunField, E as EnumRunField, F as FileRunField, L as ListRunField, N as NumberRunField, O as ObjectRunField, P as ProseRunField, T as TextRunField, U as UnknownRunField, C as ConceptCategory } from '../descriptor-X6MwFIWu.js';
4
+
5
+ /**
6
+ * Ambient state that any field in the tree may need, threaded unchanged through
7
+ * the recursion so containers don't have to re-plumb it for every child.
8
+ */
9
+ interface FieldEnv {
10
+ disabled?: boolean;
11
+ /** A file field asks its parent to upload; the parent sets the value later. */
12
+ onDropFile?: (id: string, file: File) => void;
13
+ /** Ids currently mid-upload. */
14
+ uploadingIds?: ReadonlySet<string>;
15
+ /** Resolve a `pipelex-storage://` URI to a browser-viewable URL (for previews
16
+ * of already-stored files). */
17
+ resolveUrl?: (uri: string) => Promise<string | null>;
18
+ }
19
+ interface FieldRendererProps {
20
+ field: RunField;
21
+ value: unknown;
22
+ onChange: (value: unknown) => void;
23
+ /** Unique, stable id used for label linkage and upload tracking. */
24
+ id: string;
25
+ error?: string;
26
+ env?: FieldEnv;
27
+ }
28
+ /**
29
+ * The single dispatch point: a normalized `RunField` → the matching control.
30
+ * Object and list fields recurse back through here, so the whole form is one
31
+ * data-driven tree with no per-type branching anywhere else.
32
+ */
33
+ declare function FieldRenderer({ field, value, onChange, id, error, env }: FieldRendererProps): react.JSX.Element;
34
+
35
+ interface BooleanFieldProps {
36
+ field: BooleanRunField;
37
+ value: boolean | undefined;
38
+ onChange: (value: boolean) => void;
39
+ id: string;
40
+ disabled?: boolean;
41
+ }
42
+ /**
43
+ * A toggle. Unlike the other fields the label sits inline with the control on a
44
+ * single hairline row - a boolean reads better as a switchable statement than as
45
+ * a labelled box.
46
+ */
47
+ declare function BooleanField({ field, value, onChange, id, disabled }: BooleanFieldProps): react.JSX.Element;
48
+
49
+ interface DateFieldProps {
50
+ field: DateRunField;
51
+ value: string | undefined;
52
+ onChange: (value: string | undefined) => void;
53
+ id: string;
54
+ error?: string;
55
+ disabled?: boolean;
56
+ }
57
+ /**
58
+ * A calendar-date input. The control only asks for a day (no hand-typed
59
+ * hours/seconds); for a `date-time` concept it stores midnight-UTC so the value
60
+ * still satisfies the API's `date-time` format.
61
+ */
62
+ declare function DateField({ field, value, onChange, id, error, disabled }: DateFieldProps): react.JSX.Element;
63
+
64
+ interface EnumFieldProps {
65
+ field: EnumRunField;
66
+ value: string | undefined;
67
+ onChange: (value: string | undefined) => void;
68
+ id: string;
69
+ error?: string;
70
+ disabled?: boolean;
71
+ }
72
+ /**
73
+ * Pick-one. A short option set (≤4, each label brief) renders as a segmented
74
+ * control - every choice visible at a glance - and falls back to a select once
75
+ * the set grows past what fits on a row.
76
+ */
77
+ declare function EnumField({ field, value, onChange, id, error, disabled }: EnumFieldProps): react.JSX.Element;
78
+
79
+ /** The serializable value of a file field. URL is what the runner sends. */
80
+ interface FileValue {
81
+ url?: string;
82
+ filename?: string;
83
+ }
84
+ interface FileFieldProps {
85
+ field: FileRunField;
86
+ value: FileValue | undefined;
87
+ /** Called when a file is dropped/picked - the parent uploads and sets value. */
88
+ onDropFile: (file: File) => void;
89
+ /** Manual URL paste / clear flows through here too. */
90
+ onChange: (value: FileValue | undefined) => void;
91
+ id: string;
92
+ /** Parent-driven async state. */
93
+ uploading?: boolean;
94
+ /** Resolve a `pipelex-storage://` URI to a viewable URL (stored-file preview). */
95
+ resolveUrl?: (uri: string) => Promise<string | null>;
96
+ error?: string;
97
+ disabled?: boolean;
98
+ }
99
+ /** Document variant: a file shown as a PDF preview (or chip for other docs). */
100
+ declare function DocumentField(props: FileFieldProps): react.JSX.Element;
101
+ /** Image variant: a file shown as a real image preview. */
102
+ declare function ImageField(props: FileFieldProps): react.JSX.Element;
103
+
104
+ interface ListFieldProps {
105
+ field: ListRunField;
106
+ value: unknown[] | undefined;
107
+ onChange: (value: unknown[]) => void;
108
+ id: string;
109
+ error?: string;
110
+ env?: FieldEnv;
111
+ }
112
+ /**
113
+ * A repeatable list of one element type. Each item is an indexed row with its
114
+ * own remove control; "Add" appends a fresh entry. The element renders through
115
+ * the same `FieldRenderer`, so a list of documents gets dropzones and a list of
116
+ * structured concepts gets nested cards - no special-casing per element type.
117
+ */
118
+ declare function ListField({ field, value, onChange, id, error, env }: ListFieldProps): react.JSX.Element;
119
+
120
+ interface NumberFieldProps {
121
+ field: NumberRunField;
122
+ value: number | undefined;
123
+ onChange: (value: number | undefined) => void;
124
+ id: string;
125
+ error?: string;
126
+ disabled?: boolean;
127
+ }
128
+ /** Numeric input with steppers and min/max clamping. */
129
+ declare function NumberField({ field, value, onChange, id, error, disabled }: NumberFieldProps): react.JSX.Element;
130
+
131
+ interface ObjectFieldProps {
132
+ field: ObjectRunField;
133
+ value: Record<string, unknown> | undefined;
134
+ onChange: (value: Record<string, unknown>) => void;
135
+ id: string;
136
+ error?: string;
137
+ env?: FieldEnv;
138
+ }
139
+ /**
140
+ * A structured concept: its sub-fields rendered inside one hairline-grouped
141
+ * card so nesting stays legible. Empty optional sub-fields collapse behind a
142
+ * "+ N optional" toggle - required and already-filled fields always show.
143
+ */
144
+ declare function ObjectField({ field, value, onChange, id, error, env }: ObjectFieldProps): react.JSX.Element;
145
+
146
+ interface TextFieldProps {
147
+ field: TextRunField;
148
+ value: string | undefined;
149
+ onChange: (value: string | undefined) => void;
150
+ id: string;
151
+ error?: string;
152
+ disabled?: boolean;
153
+ }
154
+ /** Single-line string input (short native.Text or a plain string property). */
155
+ declare function TextField({ field, value, onChange, id, error, disabled }: TextFieldProps): react.JSX.Element;
156
+ interface ProseFieldProps {
157
+ field: ProseRunField;
158
+ value: string | undefined;
159
+ onChange: (value: string | undefined) => void;
160
+ id: string;
161
+ error?: string;
162
+ disabled?: boolean;
163
+ }
164
+ /**
165
+ * Multi-line free text. Auto-grows with content (`field-sizing-content`) so long
166
+ * prompts don't trap the user in a tiny scrollbox - the field that carries most
167
+ * of a method's intent should feel roomy.
168
+ */
169
+ declare function ProseField({ field, value, onChange, id, error, disabled }: ProseFieldProps): react.JSX.Element;
170
+
171
+ interface UnknownFieldProps {
172
+ field: UnknownRunField;
173
+ value: string | undefined;
174
+ onChange: (value: string | undefined) => void;
175
+ id: string;
176
+ error?: string;
177
+ disabled?: boolean;
178
+ }
179
+ /**
180
+ * Escape hatch for a concept whose shape we can't map to a known control. Rather
181
+ * than block the run, we let the user hand-write the value as JSON - rare, but it
182
+ * keeps every method runnable instead of dead-ending on an unusual input.
183
+ */
184
+ declare function UnknownField({ field, value, onChange, id, error, disabled }: UnknownFieldProps): react.JSX.Element;
185
+
186
+ interface FieldShellProps {
187
+ name: string;
188
+ title?: string;
189
+ conceptRef?: string;
190
+ category: ConceptCategory;
191
+ description?: string;
192
+ required: boolean;
193
+ /** Validation message; renders in danger text under the control. */
194
+ error?: string;
195
+ /** Links the rendered name to the control for screen readers. */
196
+ htmlFor?: string;
197
+ children: ReactNode;
198
+ className?: string;
199
+ }
200
+ /**
201
+ * The label / type / description / error chrome around a single input control.
202
+ * Every field type composes this so the form reads as one consistent column -
203
+ * the control itself is the only thing that varies.
204
+ */
205
+ declare function FieldShell({ name, title, conceptRef, category, description, required, error, htmlFor, children, className, }: FieldShellProps): react.JSX.Element;
206
+
207
+ interface ConceptPillProps {
208
+ /** The concept identifier, e.g. `native.Document`, `demo.Invoice`. */
209
+ conceptRef?: string;
210
+ category: ConceptCategory;
211
+ className?: string;
212
+ }
213
+ /**
214
+ * The small monospace type tag shown beside an input's name. Mono + a line icon
215
+ * (never a colored chip) keeps the technical Pipelex voice without spending a
216
+ * chromatic accent - the concept *type* is information, not a status.
217
+ */
218
+ declare function ConceptPill({ conceptRef, category, className }: ConceptPillProps): react.JSX.Element;
219
+
220
+ interface OptionalToggleProps {
221
+ /** How many empty optional entries are currently hidden. */
222
+ count: number;
223
+ expanded: boolean;
224
+ onToggle: () => void;
225
+ /** "field" inside a concept, "input" at the top level. */
226
+ noun?: 'field' | 'input';
227
+ }
228
+ /**
229
+ * The disclosure for empty optional entries. Optional inputs stay hidden until
230
+ * the user asks for them - required (and already-filled) entries always show, so
231
+ * the form opens at its simplest and grows only on demand. Used at the top level
232
+ * and inside every structured concept, at any nesting depth.
233
+ */
234
+ declare function OptionalToggle({ count, expanded, onToggle, noun }: OptionalToggleProps): react.JSX.Element;
235
+
236
+ declare const fieldControlClass: string;
237
+
238
+ interface FieldStrings {
239
+ /** Badge on an optional field's header row. */
240
+ optionalBadge: string;
241
+ selectPlaceholder: string;
242
+ typeValuePlaceholder: string;
243
+ writeHerePlaceholder: string;
244
+ /** Helper under the raw-JSON escape hatch. */
245
+ jsonHint: string;
246
+ decrease: string;
247
+ increase: string;
248
+ noItemsYet: string;
249
+ addItem: string;
250
+ removeItemAria: (index: number) => string;
251
+ itemsCount: (count: number) => string;
252
+ uploading: string;
253
+ dropToUpload: string;
254
+ dropOrBrowse: string;
255
+ pasteUrlInstead: string;
256
+ urlPlaceholder: string;
257
+ uploadedFile: string;
258
+ preview: string;
259
+ removeFileAria: string;
260
+ previewUnavailablePdf: string;
261
+ /** The optional-entries disclosure ("field" inside a concept, "input" at top level). */
262
+ hideOptionalFields: string;
263
+ hideOptionalInputs: string;
264
+ optionalFieldsCount: (count: number) => string;
265
+ optionalInputsCount: (count: number) => string;
266
+ }
267
+ declare const DEFAULT_FIELD_STRINGS: FieldStrings;
268
+ /** Inject host-app strings over the English defaults for every control below. */
269
+ declare function FieldStringsProvider({ strings, children, }: {
270
+ strings: Partial<FieldStrings>;
271
+ children: ReactNode;
272
+ }): react.JSX.Element;
273
+ /** The strings the nearest provider supplies (English defaults when none does). */
274
+ declare function useFieldStrings(): FieldStrings;
275
+
276
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, type FieldEnv, FieldRenderer, type FieldRendererProps, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, useFieldStrings };