@pipelex/mthds-form 0.1.0 → 0.2.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.
@@ -109,7 +109,7 @@ interface Readiness {
109
109
  * a plural slot is never "absent" in MTHDS, its empty form IS the empty list,
110
110
  * and no method can declare "at least one item".
111
111
  *
112
- * **For a field the app built, the answer comes from `inputMustBeFilled`**
112
+ * **For a field this package built, the answer comes from `inputMustBeFilled`**
113
113
  * (`contracts.ts`) - the SAME predicate the method viewer's Run button, its ajv
114
114
  * `required` list and its missing-inputs toast all read. `buildRunFields`
115
115
  * stamps it onto `gating`. This used to be re-derived here as
@@ -121,8 +121,8 @@ interface Readiness {
121
121
  *
122
122
  * The shape heuristic survives only as the fallback for a **hand-authored**
123
123
  * `RunField` - story fixtures and unit tests, which have no contract to consult
124
- * and therefore nothing to disagree with. Every field the app renders comes from
125
- * `buildRunFields` and carries `gating`, so the app is on the contract's answer
124
+ * and therefore nothing to disagree with. Every field a host renders comes from
125
+ * `buildRunFields` and carries `gating`, so a host is on the contract's answer
126
126
  * alone.
127
127
  */
128
128
  declare function mustBeFilled(field: RunField): boolean;
@@ -130,10 +130,10 @@ declare function mustBeFilled(field: RunField): boolean;
130
130
  declare function computeReadiness(fields: RunField[], values: Record<string, unknown>): Readiness;
131
131
 
132
132
  /**
133
- * One validation error, in the shape the app reads (`property`, `message`,
133
+ * One validation error, in the shape hosts read (`property`, `message`,
134
134
  * `name`, `stack`, `params.format`). Structurally a superset-compatible twin of
135
- * RJSF's `RJSFValidationError`, so surfaces still holding RJSF form errors (the
136
- * method editor's Inputs panel) can hand them to the same message renderer.
135
+ * RJSF's `RJSFValidationError`, so a host surface still holding RJSF form errors
136
+ * (an RJSF-based inputs panel) can hand them to the same message renderer.
137
137
  */
138
138
  interface RunInputError {
139
139
  /** The ajv keyword that failed (`required`, `format`, `type`, …). */
@@ -218,7 +218,7 @@ declare function validateRunInputs(preparedData: Dict$1, inputs: Record<string,
218
218
  declare function apiInputsFromSchemaData(preparedData: Dict$1, inputs: Record<string, PipeInputContract>): Dict$1;
219
219
 
220
220
  /**
221
- * Bridges the `MethodRunner`'s field values to the rest of the app.
221
+ * Bridges the `MethodRunner`'s field values to the rest of the host.
222
222
  *
223
223
  * The method-store keeps inputs in the *simplified* format and runs send the
224
224
  * *full* RJSF form shape - both already handled by `inflate/deflateAllInputs`.
@@ -477,7 +477,7 @@ declare function toDateInputValue(value: string | undefined): string;
477
477
  declare function toStoredDateValue(day: string, datetime: boolean): string | undefined;
478
478
 
479
479
  /** The message keys this module renders, relative to the caller's namespace
480
- * (the app passes its `method.editor` translator). */
480
+ * (a host passes its own translator for that namespace). */
481
481
  type ValidationMessageKey = 'inputPanel.aDateField' | 'inputPanel.pickValidDate' | 'inputPanel.pickValidDateEmpty' | 'inputPanel.dateCarriesTime' | 'inputPanel.invalidValue' | 'inputPanel.invalidValueWithData';
482
482
  /**
483
483
  * The translate function the caller injects. Typed on the EXACT keys this
@@ -1,3 +1,3 @@
1
- export { apiInputsFromRunValues, apiInputsFromSchemaData, asCalendarDate, buildPipeRef, buildRunFields, buildRunInputsSchema, collapseNullable, collectSchemaDefs, computeReadiness, conceptCategory, dayWithinDateTime, deflateAllInputs, deflateInput, derefSchema, describeValidationError, displayValue, fieldFilled, fieldsForContract, flattenAnyOf, getPipeIOContract, healStringWrappers, hoistDefsToRoot, inflateAllInputs, inflateInput, inputDataFromWorkingMemory, inputMustBeFilled, isAcceptableDate, isAcceptableDateTime, isFilled, isPluralInput, mustBeFilled, normalizeSchemaForRjsf, outputsFromPipeOutput, prepareRunInputs, prepareSchemaForRjsf, pruneEmptyOptionals, resolveConceptCode, resolveSchemaIndirection, rjsfDataFromRunValues, runValuesFromStore, schemaTypeOf, setValueAtPath, storeInputDataFromRunValues, toDateInputValue, toStoredDateValue, validateRunInputs, validateRunInputsSchema, valueAtProperty } from '../chunk-DAP47OGP.js';
1
+ export { apiInputsFromRunValues, apiInputsFromSchemaData, asCalendarDate, buildPipeRef, buildRunFields, buildRunInputsSchema, collapseNullable, collectSchemaDefs, computeReadiness, conceptCategory, dayWithinDateTime, deflateAllInputs, deflateInput, derefSchema, describeValidationError, displayValue, fieldFilled, fieldsForContract, flattenAnyOf, getPipeIOContract, healStringWrappers, hoistDefsToRoot, inflateAllInputs, inflateInput, inputDataFromWorkingMemory, inputMustBeFilled, isAcceptableDate, isAcceptableDateTime, isFilled, isPluralInput, mustBeFilled, normalizeSchemaForRjsf, outputsFromPipeOutput, prepareRunInputs, prepareSchemaForRjsf, pruneEmptyOptionals, resolveConceptCode, resolveSchemaIndirection, rjsfDataFromRunValues, runValuesFromStore, schemaTypeOf, setValueAtPath, storeInputDataFromRunValues, toDateInputValue, toStoredDateValue, validateRunInputs, validateRunInputsSchema, valueAtProperty } from '../chunk-YWMUM7BC.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -273,4 +273,34 @@ declare function FieldStringsProvider({ strings, children, }: {
273
273
  /** The strings the nearest provider supplies (English defaults when none does). */
274
274
  declare function useFieldStrings(): FieldStrings;
275
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 };
276
+ /**
277
+ * How a field's LABEL chrome should read. The control itself never changes.
278
+ *
279
+ * - `studio` (default): the field's name verbatim in mono, with its concept
280
+ * pill. The name IS the identifier a builder writes in the `.mthds` file, so
281
+ * showing it exactly - and typed - is the point.
282
+ * - `app`: the field's name as a human question in sans, no concept pill. Inside
283
+ * a method app, `full_name` and `native.Text` are implementation detail; the
284
+ * person filling the form has never seen the method's source.
285
+ *
286
+ * Deliberately a context rather than a prop or a `FieldEnv` flag: only the
287
+ * components that own label chrome read it - `FieldShell`, `ObjectField`,
288
+ * `BooleanField` and `ListField` - and threading a prop would have meant
289
+ * editing every field control and both recursive containers.
290
+ */
291
+ type FieldPresentation = 'studio' | 'app';
292
+ declare function FieldPresentationProvider({ presentation, children, }: {
293
+ presentation: FieldPresentation;
294
+ children: ReactNode;
295
+ }): react.JSX.Element;
296
+ declare function useFieldPresentation(): FieldPresentation;
297
+ /**
298
+ * `full_name` → `Full name`. Only the first word is capitalised: these are
299
+ * labels, not titles, and Title Case on every word reads as a form from 2009.
300
+ * Takes IDENTIFIERS only - snake_case or kebab-case names, where a separator
301
+ * is structure. An authored `title` never goes through here: it is already
302
+ * human-facing, and its hyphens are spelling (`E-mail address`), not structure.
303
+ */
304
+ declare function humanizeFieldName(name: string): string;
305
+
306
+ 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 };
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { toStoredDateValue, toDateInputValue, isFilled } from '../chunk-DAP47OGP.js';
2
+ import { toStoredDateValue, toDateInputValue, isFilled } from '../chunk-YWMUM7BC.js';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image, FileText, CalendarDays, Type, Loader2, Upload, Link2, ImageOff, EyeOff, Eye, X, Minus, Plus, Trash2 } from 'lucide-react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import * as React2 from 'react';
8
- import { createContext, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
8
+ import { createContext, use, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
9
9
  import * as SwitchPrimitives from '@radix-ui/react-switch';
10
10
  import { useDropzone } from 'react-dropzone';
11
11
  import * as SelectPrimitive from '@radix-ui/react-select';
@@ -44,6 +44,25 @@ function ConceptPill({ conceptRef, category, className }) {
44
44
  }
45
45
  );
46
46
  }
47
+ var FieldPresentationContext = createContext("studio");
48
+ function FieldPresentationProvider({
49
+ presentation,
50
+ children
51
+ }) {
52
+ return /* @__PURE__ */ jsx(FieldPresentationContext, { value: presentation, children });
53
+ }
54
+ function useFieldPresentation() {
55
+ return use(FieldPresentationContext);
56
+ }
57
+ function humanizeFieldName(name) {
58
+ const words = name.replace(/[_-]+/g, " ").trim();
59
+ if (words === "") return name;
60
+ return words.charAt(0).toUpperCase() + words.slice(1);
61
+ }
62
+ function fieldLabel(title, name, presentation) {
63
+ if (title !== void 0) return title;
64
+ return presentation === "app" ? humanizeFieldName(name) : name;
65
+ }
47
66
  var Switch = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
48
67
  SwitchPrimitives.Root,
49
68
  {
@@ -65,6 +84,8 @@ var Switch = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
65
84
  ));
66
85
  Switch.displayName = SwitchPrimitives.Root.displayName;
67
86
  function BooleanField({ field, value, onChange, id, disabled }) {
87
+ const presentation = useFieldPresentation();
88
+ const isApp = presentation === "app";
68
89
  return /* @__PURE__ */ jsxs(
69
90
  "div",
70
91
  {
@@ -79,11 +100,14 @@ function BooleanField({ field, value, onChange, id, disabled }) {
79
100
  "label",
80
101
  {
81
102
  htmlFor: id,
82
- className: "font-mono text-[13px] font-medium leading-none text-foreground",
83
- children: field.title ?? field.name
103
+ className: cn(
104
+ "text-[13px] font-medium leading-none text-foreground",
105
+ isApp ? "text-[13.5px]" : "font-mono"
106
+ ),
107
+ children: fieldLabel(field.title, field.name, presentation)
84
108
  }
85
109
  ),
86
- /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "boolean" })
110
+ !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "boolean" })
87
111
  ] }),
88
112
  field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description })
89
113
  ] }),
@@ -151,8 +175,10 @@ function FieldShell({
151
175
  className
152
176
  }) {
153
177
  const s = useFieldStrings();
178
+ const presentation = useFieldPresentation();
179
+ const isApp = presentation === "app";
154
180
  const Label2 = htmlFor ? "label" : "div";
155
- const labelText = title !== void 0 ? title : name;
181
+ const labelText = fieldLabel(title, name, presentation);
156
182
  const showLabel = labelText.trim() !== "";
157
183
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
158
184
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
@@ -160,15 +186,18 @@ function FieldShell({
160
186
  Label2,
161
187
  {
162
188
  ...htmlFor ? { htmlFor } : {},
163
- className: "font-mono text-[13px] font-medium leading-none text-foreground",
189
+ className: cn(
190
+ "text-[13px] font-medium leading-none text-foreground",
191
+ isApp ? "text-[13.5px]" : "font-mono"
192
+ ),
164
193
  children: labelText
165
194
  }
166
195
  ),
167
- /* @__PURE__ */ jsx(ConceptPill, { conceptRef, category }),
196
+ !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef, category }),
168
197
  !required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
169
198
  ] }),
170
199
  description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: description }),
171
- children,
200
+ isApp && error ? /* @__PURE__ */ jsx("div", { className: "rounded-lg ring-1 ring-destructive/70 ring-offset-4 ring-offset-background", children }) : children,
172
201
  error && /* @__PURE__ */ jsx("p", { className: "flex items-center gap-1 text-[11px] text-destructive", role: "alert", children: error })
173
202
  ] });
174
203
  }
@@ -809,14 +838,25 @@ function OptionalToggle({ count, expanded, onToggle, noun = "field" }) {
809
838
  function ObjectField({ field, value, onChange, id, error, env }) {
810
839
  const s = useFieldStrings();
811
840
  const [showOptional, setShowOptional] = useState(false);
841
+ const presentation = useFieldPresentation();
842
+ const isApp = presentation === "app";
812
843
  const data = value ?? {};
813
844
  const setChild = (name, childValue) => onChange({ ...data, [name]: childValue });
814
845
  const optionalEmpty = field.fields.filter((f) => !f.required && !isFilled(data[f.name]));
815
846
  const visible = showOptional ? field.fields : field.fields.filter((f) => f.required || isFilled(data[f.name]));
816
847
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
817
848
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
818
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[13px] font-medium leading-none text-foreground", children: field.title ?? field.name }),
819
- /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "structured" }),
849
+ /* @__PURE__ */ jsx(
850
+ "span",
851
+ {
852
+ className: cn(
853
+ "text-[13px] font-medium leading-none text-foreground",
854
+ isApp ? "text-[13.5px]" : "font-mono"
855
+ ),
856
+ children: fieldLabel(field.title, field.name, presentation)
857
+ }
858
+ ),
859
+ !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "structured" }),
820
860
  !field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
821
861
  ] }),
822
862
  field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
@@ -856,6 +896,8 @@ function ObjectField({ field, value, onChange, id, error, env }) {
856
896
  }
857
897
  function ListField({ field, value, onChange, id, error, env }) {
858
898
  const s = useFieldStrings();
899
+ const presentation = useFieldPresentation();
900
+ const isApp = presentation === "app";
859
901
  const items = value ?? [];
860
902
  const setItem = (index, itemValue) => {
861
903
  const next = items.slice();
@@ -866,8 +908,17 @@ function ListField({ field, value, onChange, id, error, env }) {
866
908
  const addItem = () => onChange([...items, emptyValue(field.item)]);
867
909
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
868
910
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
869
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[13px] font-medium leading-none text-foreground", children: field.title ?? field.name }),
870
- /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "list" }),
911
+ /* @__PURE__ */ jsx(
912
+ "span",
913
+ {
914
+ className: cn(
915
+ "text-[13px] font-medium leading-none text-foreground",
916
+ isApp ? "text-[13.5px]" : "font-mono"
917
+ ),
918
+ children: fieldLabel(field.title, field.name, presentation)
919
+ }
920
+ ),
921
+ !isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "list" }),
871
922
  /* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: s.itemsCount(items.length) }),
872
923
  !field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
873
924
  ] }),
@@ -1113,6 +1164,6 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
1113
1164
  }
1114
1165
  }
1115
1166
 
1116
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldRenderer, FieldShell, FieldStringsProvider, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, useFieldStrings };
1167
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, humanizeFieldName, useFieldPresentation, useFieldStrings };
1117
1168
  //# sourceMappingURL=index.js.map
1118
1169
  //# sourceMappingURL=index.js.map