@resistdesign/voltra 3.0.0-alpha.23 → 3.0.0-alpha.25

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,73 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Shared AutoForm UI components driven by renderer suites.
5
+ */
6
+ import { FC } from "react";
7
+ import type { ReactElement } from "react";
8
+ import type { TypeInfo, TypeOperation } from "../../common/TypeParsing/TypeInfo";
9
+ import type { AutoFieldProps, CustomTypeActionPayload, FormController, FormValues, RelationActionPayload } from "./types";
10
+ import type { ResolvedSuite } from "./core/types";
11
+ /**
12
+ * Renderer contract used by shared AutoForm components.
13
+ */
14
+ export interface AutoFormRenderer {
15
+ /** Suite-backed field component for each field controller. */
16
+ AutoField: FC<AutoFieldProps>;
17
+ /** Resolved suite that provides primitives for container controls. */
18
+ suite: ResolvedSuite<ReactElement>;
19
+ }
20
+ /**
21
+ * Props for the shared AutoFormView component.
22
+ */
23
+ export interface AutoFormViewProps {
24
+ /** Prepared controller that supplies field state. */
25
+ controller: FormController;
26
+ /** Submit handler invoked with validated form values. */
27
+ onSubmit: (values: FormValues) => void;
28
+ /** Renderer containing AutoField and suite primitives. */
29
+ renderer: AutoFormRenderer;
30
+ /** Disable the submit button when true. */
31
+ submitDisabled?: boolean;
32
+ /** Optional relation action handler for reference fields. */
33
+ onRelationAction?: (payload: RelationActionPayload) => void;
34
+ /** Optional custom type action handler. */
35
+ onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
36
+ }
37
+ /**
38
+ * Render a form UI from a prepared form controller.
39
+ *
40
+ * @param props - View props including controller and callbacks.
41
+ * @returns Rendered form view.
42
+ */
43
+ export declare const AutoFormView: FC<AutoFormViewProps>;
44
+ /**
45
+ * Props for the shared AutoForm component.
46
+ */
47
+ export interface AutoFormProps {
48
+ /** Type metadata used to build the form. */
49
+ typeInfo: TypeInfo;
50
+ /** Submit handler invoked with validated form values. */
51
+ onSubmit: (values: FormValues) => void;
52
+ /** Renderer containing AutoField and suite primitives. */
53
+ renderer: AutoFormRenderer;
54
+ /** Optional initial values applied before defaults. */
55
+ initialValues?: FormValues;
56
+ /** Optional change handler invoked when values update. */
57
+ onValuesChange?: (values: FormValues) => void;
58
+ /** Optional relation action handler for reference fields. */
59
+ onRelationAction?: (payload: RelationActionPayload) => void;
60
+ /** Optional custom type action handler. */
61
+ onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
62
+ /** Optional operation override for field state. */
63
+ operation?: TypeOperation;
64
+ /** Disable the submit button when true. */
65
+ submitDisabled?: boolean;
66
+ }
67
+ /**
68
+ * Build a controller from type metadata and render an auto form.
69
+ *
70
+ * @param props - Auto form props including type info and callbacks.
71
+ * @returns Rendered form bound to a new controller.
72
+ */
73
+ export declare const AutoForm: FC<AutoFormProps>;
@@ -110,6 +110,11 @@ export type PrimitiveComponent<Props, RenderOutput = unknown> = (props: Props) =
110
110
  * Primitive components that suites may override.
111
111
  */
112
112
  export type PrimitiveComponents<RenderOutput = unknown> = {
113
+ /** Root container for the form view. */
114
+ FormRoot: PrimitiveComponent<{
115
+ children: RenderOutput;
116
+ onSubmit?: () => void;
117
+ }, RenderOutput>;
113
118
  /** Wrapper for grouped field content. */
114
119
  FieldWrapper: PrimitiveComponent<{
115
120
  children: RenderOutput;
@@ -7,3 +7,4 @@ export * from "./types";
7
7
  export type { ComponentSuite, FieldKind, FieldRenderContext, FieldRenderer, FieldValue, PrimitiveComponent, PrimitiveComponents, ResolvedSuite, } from "./core";
8
8
  export { createAutoField, createFormRenderer, getFieldKind, mergeSuites, resolveSuite, withRendererOverride, } from "./core";
9
9
  export * from "./Engine";
10
+ export * from "./UI";
package/app/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext, useRef, useMemo, useCallback, useState, useEffect } from 'react';
2
- import { jsx } from 'react/jsx-runtime';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
 
4
4
  // src/app/utils/ApplicationState.tsx
5
5
  var getApplicationStateIdentifier = (subStateIdMap) => subStateIdMap ? subStateIdMap : {};
@@ -1651,5 +1651,86 @@ var useFormEngine = (initialValues = {}, typeInfo, options) => {
1651
1651
  setErrors
1652
1652
  };
1653
1653
  };
1654
+ var fallbackFormRoot = ({
1655
+ children,
1656
+ onSubmit
1657
+ }) => {
1658
+ const handleSubmit = (event) => {
1659
+ event.preventDefault();
1660
+ onSubmit?.();
1661
+ };
1662
+ return /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children });
1663
+ };
1664
+ var fallbackButton = ({
1665
+ children,
1666
+ disabled,
1667
+ type,
1668
+ onClick
1669
+ }) => {
1670
+ return /* @__PURE__ */ jsx("button", { type: type ?? "button", disabled, onClick, children });
1671
+ };
1672
+ var AutoFormView = ({
1673
+ controller,
1674
+ onSubmit,
1675
+ renderer,
1676
+ submitDisabled,
1677
+ onRelationAction,
1678
+ onCustomTypeAction
1679
+ }) => {
1680
+ const FormRoot = renderer.suite.primitives?.FormRoot ?? fallbackFormRoot;
1681
+ const Button = renderer.suite.primitives?.Button ?? fallbackButton;
1682
+ const AutoField = renderer.AutoField;
1683
+ const submit = () => {
1684
+ if (controller.validate()) {
1685
+ onSubmit(controller.values);
1686
+ }
1687
+ };
1688
+ return /* @__PURE__ */ jsx(FormRoot, { onSubmit: submit, children: /* @__PURE__ */ jsxs(Fragment, { children: [
1689
+ controller.fields.filter((fieldController) => !fieldController.hidden).map((fieldController) => /* @__PURE__ */ jsx(
1690
+ AutoField,
1691
+ {
1692
+ field: fieldController.field,
1693
+ fieldKey: fieldController.key,
1694
+ value: fieldController.value,
1695
+ onChange: fieldController.onChange,
1696
+ error: fieldController.error,
1697
+ onRelationAction,
1698
+ disabled: fieldController.disabled,
1699
+ onCustomTypeAction
1700
+ },
1701
+ fieldController.key
1702
+ )),
1703
+ /* @__PURE__ */ jsx(Button, { type: "submit", onClick: submit, disabled: submitDisabled, children: /* @__PURE__ */ jsx(Fragment, { children: "Submit" }) })
1704
+ ] }) });
1705
+ };
1706
+ var AutoForm = ({
1707
+ typeInfo,
1708
+ onSubmit,
1709
+ renderer,
1710
+ initialValues,
1711
+ onValuesChange,
1712
+ onRelationAction,
1713
+ onCustomTypeAction,
1714
+ operation,
1715
+ submitDisabled
1716
+ }) => {
1717
+ const controller = useFormEngine(initialValues, typeInfo, { operation });
1718
+ useEffect(() => {
1719
+ if (onValuesChange) {
1720
+ onValuesChange(controller.values);
1721
+ }
1722
+ }, [controller.values, onValuesChange]);
1723
+ return /* @__PURE__ */ jsx(
1724
+ AutoFormView,
1725
+ {
1726
+ controller,
1727
+ onSubmit,
1728
+ renderer,
1729
+ onRelationAction,
1730
+ onCustomTypeAction,
1731
+ submitDisabled
1732
+ }
1733
+ );
1734
+ };
1654
1735
 
1655
- export { ApplicationStateContext, ApplicationStateProvider, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, TypeInfoORMClient, buildHistoryPath, buildQueryString, buildRoutePath, canUseBrowserHistory, computeAreaBounds, computeTrackPixels, createAutoField, createBrowserRouteAdapter, createEasyLayout, createFormRenderer, createManualRouteAdapter, createMemoryHistory, createNativeRouteAdapter, createRouteAdapterFromHistory, createUniversalAdapter, getApplicationStateIdentifier, getApplicationStateModified, getApplicationStateValue, getApplicationStateValueStructure, getChangedDependencyIndexes, getEasyLayoutTemplateDetails, getFieldKind, getFullUrl, getPascalCaseAreaName, handleRequest, mergeSuites, parseHistoryPath, parseTemplate, requestHandlerFactory, resolveSuite, sendServiceRequest, setApplicationStateModified, setApplicationStateValue, useApplicationStateLoader, useApplicationStateValue, useApplicationStateValueStructure, useController, useDebugDependencies, useFormEngine, useRouteContext, useTypeInfoORMAPI, validateAreas, withRendererOverride };
1736
+ export { ApplicationStateContext, ApplicationStateProvider, AutoForm, AutoFormView, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, TypeInfoORMClient, buildHistoryPath, buildQueryString, buildRoutePath, canUseBrowserHistory, computeAreaBounds, computeTrackPixels, createAutoField, createBrowserRouteAdapter, createEasyLayout, createFormRenderer, createManualRouteAdapter, createMemoryHistory, createNativeRouteAdapter, createRouteAdapterFromHistory, createUniversalAdapter, getApplicationStateIdentifier, getApplicationStateModified, getApplicationStateValue, getApplicationStateValueStructure, getChangedDependencyIndexes, getEasyLayoutTemplateDetails, getFieldKind, getFullUrl, getPascalCaseAreaName, handleRequest, mergeSuites, parseHistoryPath, parseTemplate, requestHandlerFactory, resolveSuite, sendServiceRequest, setApplicationStateModified, setApplicationStateValue, useApplicationStateLoader, useApplicationStateValue, useApplicationStateValueStructure, useController, useDebugDependencies, useFormEngine, useRouteContext, useTypeInfoORMAPI, validateAreas, withRendererOverride };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Native AutoForm wrappers backed by the default native renderer.
5
+ */
6
+ import type { FC } from "react";
7
+ import type { AutoFieldProps, CustomTypeActionPayload, FormController, FormValues, RelationActionPayload } from "../../app/forms/types";
8
+ import type { TypeInfo, TypeOperation } from "../../common/TypeParsing/TypeInfo";
9
+ /**
10
+ * Render a form field based on TypeInfo metadata.
11
+ *
12
+ * @category Forms
13
+ *
14
+ * @param props - AutoField props describing the field and handlers.
15
+ * @returns Rendered field UI.
16
+ */
17
+ export declare const AutoField: FC<AutoFieldProps>;
18
+ /**
19
+ * Props for the AutoFormView component.
20
+ */
21
+ export interface AutoFormViewProps {
22
+ /** Prepared controller that supplies field state. */
23
+ controller: FormController;
24
+ /** Submit handler invoked with validated form values. */
25
+ onSubmit: (values: FormValues) => void;
26
+ /** Disable the submit button when true. */
27
+ submitDisabled?: boolean;
28
+ /** Optional relation action handler for reference fields. */
29
+ onRelationAction?: (payload: RelationActionPayload) => void;
30
+ /** Optional custom type action handler. */
31
+ onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
32
+ }
33
+ /**
34
+ * Render a native form UI from a prepared form controller.
35
+ *
36
+ * @param props - View props including controller and callbacks.
37
+ * @returns Rendered form view.
38
+ */
39
+ export declare const AutoFormView: FC<AutoFormViewProps>;
40
+ /**
41
+ * Props for the AutoForm component.
42
+ */
43
+ export interface AutoFormProps {
44
+ /** Type metadata used to build the form. */
45
+ typeInfo: TypeInfo;
46
+ /** Submit handler invoked with validated form values. */
47
+ onSubmit: (values: FormValues) => void;
48
+ /** Optional initial values applied before defaults. */
49
+ initialValues?: FormValues;
50
+ /** Optional change handler invoked when values update. */
51
+ onValuesChange?: (values: FormValues) => void;
52
+ /** Optional relation action handler for reference fields. */
53
+ onRelationAction?: (payload: RelationActionPayload) => void;
54
+ /** Optional custom type action handler. */
55
+ onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
56
+ /** Optional operation override for field state. */
57
+ operation?: TypeOperation;
58
+ /** Disable the submit button when true. */
59
+ submitDisabled?: boolean;
60
+ }
61
+ /**
62
+ * Build a controller from type metadata and render a native auto form.
63
+ *
64
+ * @param props - Auto form props including type info and callbacks.
65
+ * @returns Rendered native form.
66
+ */
67
+ export declare const AutoForm: FC<AutoFormProps>;
@@ -4,5 +4,6 @@
4
4
  * Native form rendering exports.
5
5
  */
6
6
  export * from "./suite";
7
+ export * from "./UI";
7
8
  export * from "./primitives";
8
9
  export * from "./createNativeFormRenderer";
@@ -3,38 +3,31 @@
3
3
  *
4
4
  * React Native primitives for the form generation system.
5
5
  */
6
+ import { Text, View } from "react-native";
6
7
  /**
7
8
  * Wrapper for grouped field content.
8
9
  */
9
10
  export declare const FieldWrapper: (props: {
10
11
  children?: React.ReactNode;
11
- }) => import("react").CElement<{}, import("react").Component<{}, any, any>>;
12
+ }) => import("react").CElement<import("react-native").ViewProps, View>;
12
13
  /**
13
14
  * Inline error message renderer.
14
15
  */
15
16
  export declare const ErrorMessage: ({ children }: {
16
17
  children: React.ReactNode;
17
- }) => import("react").CElement<{
18
- style: {
19
- color: string;
20
- };
21
- }, import("react").Component<{
22
- style: {
23
- color: string;
24
- };
25
- }, any, any>>;
18
+ }) => import("react").CElement<import("react-native").TextProps, Text>;
26
19
  /**
27
20
  * Container for array field items.
28
21
  */
29
22
  export declare const ArrayContainer: (props: {
30
23
  children?: React.ReactNode;
31
- }) => import("react").CElement<{}, import("react").Component<{}, any, any>>;
24
+ }) => import("react").CElement<import("react-native").ViewProps, View>;
32
25
  /**
33
26
  * Wrapper for an individual array item row.
34
27
  */
35
28
  export declare const ArrayItemWrapper: (props: {
36
29
  children?: React.ReactNode;
37
- }) => import("react").CElement<{}, import("react").Component<{}, any, any>>;
30
+ }) => import("react").CElement<import("react-native").ViewProps, View>;
38
31
  /**
39
32
  * Minimal button primitive.
40
33
  */
@@ -42,7 +35,4 @@ export declare const Button: ({ children, disabled, onPress, }: {
42
35
  children?: React.ReactNode;
43
36
  disabled?: boolean;
44
37
  onPress?: () => void;
45
- }) => import("react").FunctionComponentElement<{
46
- onPress: (() => void) | undefined;
47
- disabled: boolean | undefined;
48
- }>;
38
+ }) => import("react").FunctionComponentElement<import("react-native").PressableProps & import("react").RefAttributes<View>>;
package/native/index.js CHANGED
@@ -1,12 +1,8 @@
1
- import { createContext, createElement, useMemo } from 'react';
2
- import 'react/jsx-runtime';
1
+ import { createContext, createElement, useMemo, useEffect, useState, useCallback } from 'react';
2
+ import { View, Text, Pressable, TextInput, Switch, Platform } from 'react-native';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
4
 
4
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
- }) : x)(function(x) {
7
- if (typeof require !== "undefined") return require.apply(this, arguments);
8
- throw Error('Dynamic require of "' + x + '" is not supported');
9
- });
5
+ // src/native/forms/suite.ts
10
6
 
11
7
  // src/app/forms/core/getFieldKind.ts
12
8
  var hasSelectableValues = (field) => {
@@ -106,27 +102,22 @@ var createFormRenderer = (options) => {
106
102
  options.suite,
107
103
  options.fallbackSuite
108
104
  );
109
- const AutoField = createAutoField(resolvedSuite);
105
+ const AutoField2 = createAutoField(resolvedSuite);
110
106
  return {
111
- AutoField,
107
+ AutoField: AutoField2,
112
108
  suite: resolvedSuite
113
109
  };
114
110
  };
115
- var getNative = () => __require("react-native");
116
111
  var FieldWrapper = (props) => {
117
- const { View } = getNative();
118
112
  return createElement(View, null, props.children);
119
113
  };
120
114
  var ErrorMessage = ({ children }) => {
121
- const { Text } = getNative();
122
115
  return createElement(Text, { style: { color: "#AA0000" } }, children);
123
116
  };
124
117
  var ArrayContainer = (props) => {
125
- const { View } = getNative();
126
118
  return createElement(View, null, props.children);
127
119
  };
128
120
  var ArrayItemWrapper = (props) => {
129
- const { View } = getNative();
130
121
  return createElement(View, null, props.children);
131
122
  };
132
123
  var Button = ({
@@ -134,7 +125,6 @@ var Button = ({
134
125
  disabled,
135
126
  onPress
136
127
  }) => {
137
- const { Pressable, Text } = getNative();
138
128
  return createElement(
139
129
  Pressable,
140
130
  { onPress, disabled },
@@ -143,7 +133,6 @@ var Button = ({
143
133
  };
144
134
 
145
135
  // src/native/forms/suite.ts
146
- var getNative2 = () => __require("react-native");
147
136
  var createArrayItemField = (field) => ({
148
137
  ...field,
149
138
  array: false,
@@ -164,7 +153,6 @@ var formatCustomValue = (val) => {
164
153
  return JSON.stringify(val, null, 2);
165
154
  };
166
155
  var renderRelationSingle = (context) => {
167
- const { Text } = getNative2();
168
156
  const { field, fieldKey, label, required, disabled, error, onRelationAction } = context;
169
157
  return createElement(
170
158
  FieldWrapper,
@@ -189,7 +177,6 @@ var renderRelationSingle = (context) => {
189
177
  );
190
178
  };
191
179
  var renderRelationArray = (context) => {
192
- const { Text } = getNative2();
193
180
  const { field, fieldKey, label, required, disabled, error, onRelationAction } = context;
194
181
  return createElement(
195
182
  FieldWrapper,
@@ -214,7 +201,6 @@ var renderRelationArray = (context) => {
214
201
  );
215
202
  };
216
203
  var renderCustomSingle = (context) => {
217
- const { Text } = getNative2();
218
204
  const { field, fieldKey, label, required, disabled, error } = context;
219
205
  const customType = field.tags?.customType;
220
206
  const onCustomTypeAction = context.onCustomTypeAction;
@@ -242,7 +228,6 @@ var renderCustomSingle = (context) => {
242
228
  );
243
229
  };
244
230
  var renderCustomArray = (context) => {
245
- const { Text, View } = getNative2();
246
231
  const { field, fieldKey, label, required, disabled, error } = context;
247
232
  const customType = field.tags?.customType;
248
233
  const onCustomTypeAction = context.onCustomTypeAction;
@@ -318,7 +303,6 @@ var renderCustomArray = (context) => {
318
303
  };
319
304
  var autoFieldRenderer;
320
305
  var renderArray = (context) => {
321
- const { Text, View } = getNative2();
322
306
  const { field, fieldKey, label, required, disabled, error } = context;
323
307
  const itemField = createArrayItemField(field);
324
308
  const arrayValue = Array.isArray(context.value) ? [...context.value] : [];
@@ -381,7 +365,6 @@ var renderArray = (context) => {
381
365
  );
382
366
  };
383
367
  var renderString = (context) => {
384
- const { Text, TextInput } = getNative2();
385
368
  const { label, required, disabled, error } = context;
386
369
  return createElement(
387
370
  FieldWrapper,
@@ -397,7 +380,6 @@ var renderString = (context) => {
397
380
  );
398
381
  };
399
382
  var renderNumber = (context) => {
400
- const { Text, TextInput } = getNative2();
401
383
  const { label, required, disabled, error } = context;
402
384
  return createElement(
403
385
  FieldWrapper,
@@ -414,7 +396,6 @@ var renderNumber = (context) => {
414
396
  );
415
397
  };
416
398
  var renderBoolean = (context) => {
417
- const { Text, View, Switch } = getNative2();
418
399
  const { label, disabled, error } = context;
419
400
  return createElement(
420
401
  FieldWrapper,
@@ -433,7 +414,6 @@ var renderBoolean = (context) => {
433
414
  );
434
415
  };
435
416
  var renderEnumSelect = (context) => {
436
- const { Text, View } = getNative2();
437
417
  const { field, label, required, disabled, error } = context;
438
418
  const selectableValues = getSelectableValues(context.possibleValues) ?? [];
439
419
  return createElement(
@@ -460,6 +440,31 @@ var renderEnumSelect = (context) => {
460
440
  error ? createElement(ErrorMessage, null, error) : null
461
441
  );
462
442
  };
443
+ var FormRoot = ({
444
+ children,
445
+ onSubmit
446
+ }) => {
447
+ if (Platform?.OS === "web") {
448
+ return createElement(
449
+ "form",
450
+ {
451
+ onSubmit: (event) => {
452
+ event?.preventDefault?.();
453
+ onSubmit?.();
454
+ }
455
+ },
456
+ children
457
+ );
458
+ }
459
+ return createElement(View, null, children);
460
+ };
461
+ var SuiteButton = ({
462
+ children,
463
+ disabled,
464
+ onClick
465
+ }) => {
466
+ return createElement(Button, { disabled, onPress: onClick }, children);
467
+ };
463
468
  var nativeSuite = {
464
469
  renderers: {
465
470
  string: renderString,
@@ -471,11 +476,221 @@ var nativeSuite = {
471
476
  relation_array: renderRelationArray,
472
477
  custom_single: renderCustomSingle,
473
478
  custom_array: renderCustomArray
479
+ },
480
+ primitives: {
481
+ FormRoot,
482
+ FieldWrapper,
483
+ ErrorMessage,
484
+ Label: ({ children }) => createElement(Text, null, children),
485
+ Button: SuiteButton
474
486
  }
475
487
  };
476
488
  var resolvedNativeSuite = resolveSuite(void 0, nativeSuite);
477
489
  autoFieldRenderer = createAutoField(resolvedNativeSuite);
478
490
  var nativeAutoField = autoFieldRenderer;
491
+ var getDeniedOperation = (deniedOperations, operation) => {
492
+ if (!deniedOperations) {
493
+ return false;
494
+ }
495
+ const denied = deniedOperations[operation];
496
+ if (typeof denied === "boolean") {
497
+ return denied;
498
+ }
499
+ return deniedOperations[operation.toLowerCase()] ?? false;
500
+ };
501
+ var buildInitialValues = (initialValues, typeInfo) => {
502
+ const values = { ...initialValues };
503
+ for (const [key, field] of Object.entries(typeInfo.fields ?? {})) {
504
+ if (values[key] !== void 0) {
505
+ continue;
506
+ }
507
+ const defaultValue = field.tags?.constraints?.defaultValue;
508
+ if (defaultValue !== void 0) {
509
+ let parsedDefaultValue = defaultValue;
510
+ try {
511
+ parsedDefaultValue = JSON.parse(defaultValue);
512
+ } catch (error) {
513
+ }
514
+ values[key] = parsedDefaultValue;
515
+ continue;
516
+ }
517
+ if (field.array && !field.typeReference && !field.optional) {
518
+ values[key] = [];
519
+ continue;
520
+ }
521
+ if (field.type === "boolean" && !field.optional) {
522
+ values[key] = false;
523
+ }
524
+ }
525
+ return values;
526
+ };
527
+ var useFormEngine = (initialValues = {}, typeInfo, options) => {
528
+ const operation = options?.operation ?? "CREATE" /* CREATE */;
529
+ const [values, setValues] = useState(
530
+ buildInitialValues(initialValues, typeInfo)
531
+ );
532
+ const [errors, setErrors] = useState({});
533
+ const setFieldValue = useCallback((path, value) => {
534
+ setValues((prev) => {
535
+ return {
536
+ ...prev,
537
+ [path]: value
538
+ };
539
+ });
540
+ }, []);
541
+ const validate = useCallback(() => {
542
+ const newErrors = {};
543
+ for (const [key, field] of Object.entries(typeInfo.fields ?? {})) {
544
+ if (field.tags?.hidden) {
545
+ continue;
546
+ }
547
+ const val = values[key];
548
+ if (field.readonly && (val === void 0 || val === null || val === "")) {
549
+ continue;
550
+ }
551
+ const isMissing = val === void 0 || val === null || val === "" || field.array && (!Array.isArray(val) || val.length === 0);
552
+ if (!field.optional && isMissing) {
553
+ newErrors[key] = "This field is required";
554
+ continue;
555
+ }
556
+ if (isMissing) {
557
+ continue;
558
+ }
559
+ const constraints = field.tags?.constraints;
560
+ if (constraints?.pattern && typeof val === "string") {
561
+ const pattern = new RegExp(constraints.pattern);
562
+ if (!pattern.test(val)) {
563
+ newErrors[key] = "Value does not match required pattern";
564
+ continue;
565
+ }
566
+ }
567
+ if (field.type === "number" && typeof val === "number") {
568
+ if (constraints?.min !== void 0 && val < constraints.min) {
569
+ newErrors[key] = `Value must be at least ${constraints.min}`;
570
+ continue;
571
+ }
572
+ if (constraints?.max !== void 0 && val > constraints.max) {
573
+ newErrors[key] = `Value must be at most ${constraints.max}`;
574
+ continue;
575
+ }
576
+ }
577
+ }
578
+ setErrors(newErrors);
579
+ return Object.keys(newErrors).length === 0;
580
+ }, [typeInfo, values]);
581
+ const fields = useMemo(() => {
582
+ return Object.entries(typeInfo.fields ?? {}).map(([key, field]) => {
583
+ const { tags } = field;
584
+ const isPrimary = tags?.primaryField || typeInfo.primaryField === key;
585
+ return {
586
+ key,
587
+ field,
588
+ label: tags?.label ?? key,
589
+ required: !field.optional,
590
+ disabled: field.readonly || getDeniedOperation(typeInfo.tags?.deniedOperations, operation) || getDeniedOperation(tags?.deniedOperations, operation) || operation === "UPDATE" /* UPDATE */ && isPrimary,
591
+ hidden: !!tags?.hidden,
592
+ primary: isPrimary,
593
+ format: tags?.format,
594
+ constraints: tags?.constraints,
595
+ value: values[key],
596
+ onChange: (value) => setFieldValue(key, value),
597
+ error: errors[key]
598
+ };
599
+ });
600
+ }, [typeInfo, values, errors, setFieldValue, operation]);
601
+ return {
602
+ typeInfo,
603
+ typeTags: typeInfo.tags,
604
+ operation,
605
+ values,
606
+ errors,
607
+ fields,
608
+ setFieldValue,
609
+ validate,
610
+ setErrors
611
+ };
612
+ };
613
+ var fallbackFormRoot = ({
614
+ children,
615
+ onSubmit
616
+ }) => {
617
+ const handleSubmit = (event) => {
618
+ event.preventDefault();
619
+ onSubmit?.();
620
+ };
621
+ return /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children });
622
+ };
623
+ var fallbackButton = ({
624
+ children,
625
+ disabled,
626
+ type,
627
+ onClick
628
+ }) => {
629
+ return /* @__PURE__ */ jsx("button", { type: type ?? "button", disabled, onClick, children });
630
+ };
631
+ var AutoFormView = ({
632
+ controller,
633
+ onSubmit,
634
+ renderer,
635
+ submitDisabled,
636
+ onRelationAction,
637
+ onCustomTypeAction
638
+ }) => {
639
+ const FormRoot2 = renderer.suite.primitives?.FormRoot ?? fallbackFormRoot;
640
+ const Button2 = renderer.suite.primitives?.Button ?? fallbackButton;
641
+ const AutoField2 = renderer.AutoField;
642
+ const submit = () => {
643
+ if (controller.validate()) {
644
+ onSubmit(controller.values);
645
+ }
646
+ };
647
+ return /* @__PURE__ */ jsx(FormRoot2, { onSubmit: submit, children: /* @__PURE__ */ jsxs(Fragment, { children: [
648
+ controller.fields.filter((fieldController) => !fieldController.hidden).map((fieldController) => /* @__PURE__ */ jsx(
649
+ AutoField2,
650
+ {
651
+ field: fieldController.field,
652
+ fieldKey: fieldController.key,
653
+ value: fieldController.value,
654
+ onChange: fieldController.onChange,
655
+ error: fieldController.error,
656
+ onRelationAction,
657
+ disabled: fieldController.disabled,
658
+ onCustomTypeAction
659
+ },
660
+ fieldController.key
661
+ )),
662
+ /* @__PURE__ */ jsx(Button2, { type: "submit", onClick: submit, disabled: submitDisabled, children: /* @__PURE__ */ jsx(Fragment, { children: "Submit" }) })
663
+ ] }) });
664
+ };
665
+ var AutoForm = ({
666
+ typeInfo,
667
+ onSubmit,
668
+ renderer,
669
+ initialValues,
670
+ onValuesChange,
671
+ onRelationAction,
672
+ onCustomTypeAction,
673
+ operation,
674
+ submitDisabled
675
+ }) => {
676
+ const controller = useFormEngine(initialValues, typeInfo, { operation });
677
+ useEffect(() => {
678
+ if (onValuesChange) {
679
+ onValuesChange(controller.values);
680
+ }
681
+ }, [controller.values, onValuesChange]);
682
+ return /* @__PURE__ */ jsx(
683
+ AutoFormView,
684
+ {
685
+ controller,
686
+ onSubmit,
687
+ renderer,
688
+ onRelationAction,
689
+ onCustomTypeAction,
690
+ submitDisabled
691
+ }
692
+ );
693
+ };
479
694
 
480
695
  // src/native/forms/createNativeFormRenderer.ts
481
696
  var createNativeFormRenderer = (options) => {
@@ -484,6 +699,25 @@ var createNativeFormRenderer = (options) => {
484
699
  suite: options?.suite
485
700
  });
486
701
  };
702
+ var defaultNativeRenderer = createNativeFormRenderer();
703
+ var AutoField = (props) => {
704
+ return nativeAutoField({
705
+ field: props.field,
706
+ fieldKey: props.fieldKey,
707
+ value: props.value,
708
+ onChange: props.onChange,
709
+ error: props.error,
710
+ disabled: props.disabled,
711
+ onRelationAction: props.onRelationAction,
712
+ onCustomTypeAction: props.onCustomTypeAction
713
+ });
714
+ };
715
+ var AutoFormView2 = (props) => {
716
+ return /* @__PURE__ */ jsx(AutoFormView, { ...props, renderer: defaultNativeRenderer });
717
+ };
718
+ var AutoForm2 = (props) => {
719
+ return /* @__PURE__ */ jsx(AutoForm, { ...props, renderer: defaultNativeRenderer });
720
+ };
487
721
 
488
722
  // src/app/utils/History.ts
489
723
  var ensurePrefix = (value, prefix) => value ? value.startsWith(prefix) ? value : `${prefix}${value}` : "";
@@ -1121,4 +1355,4 @@ var buildPathFromRouteChain = (routeChain, config, query) => {
1121
1355
  return buildRoutePath(segments, query);
1122
1356
  };
1123
1357
 
1124
- export { ArrayContainer, ArrayItemWrapper, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, buildHistoryPath, buildPathFromRouteChain, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout };
1358
+ export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, buildHistoryPath, buildPathFromRouteChain, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resistdesign/voltra",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.25",
4
4
  "description": "With our powers combined!",
5
5
  "homepage": "https://voltra.app",
6
6
  "repository": "git@github.com:resistdesign/voltra.git",
package/web/forms/UI.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * @packageDocumentation
3
3
  *
4
- * Tier 2 UI components: AutoForm, AutoField.
4
+ * Web AutoForm wrappers backed by the default web renderer.
5
5
  */
6
- import { FC } from "react";
6
+ import type { FC } from "react";
7
+ import type { AutoFieldProps, CustomTypeActionPayload, FormValues, RelationActionPayload } from "../../app/forms/types";
7
8
  import type { TypeInfo, TypeOperation } from "../../common/TypeParsing/TypeInfo";
8
- import type { AutoFieldProps, CustomTypeActionPayload, FormController, FormValues, RelationActionPayload } from "../../app/forms/types";
9
+ import type { FormController } from "../../app/forms/types";
9
10
  /**
10
11
  * Render a form field based on TypeInfo metadata.
11
12
  *
@@ -31,7 +32,7 @@ export interface AutoFormViewProps {
31
32
  onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
32
33
  }
33
34
  /**
34
- * Render a form UI from a prepared form controller.
35
+ * Render a web form UI from a prepared form controller.
35
36
  *
36
37
  * @param props - View props including controller and callbacks.
37
38
  * @returns Rendered form view.
@@ -59,9 +60,9 @@ export interface AutoFormProps {
59
60
  submitDisabled?: boolean;
60
61
  }
61
62
  /**
62
- * Build a controller from type metadata and render an auto form.
63
+ * Build a controller from type metadata and render a web auto form.
63
64
  *
64
65
  * @param props - Auto form props including type info and callbacks.
65
- * @returns Rendered form bound to a new controller.
66
+ * @returns Rendered web form.
66
67
  */
67
68
  export declare const AutoForm: FC<AutoFormProps>;
package/web/index.js CHANGED
@@ -1,7 +1,7 @@
1
+ import { createElement, useEffect, useState, useCallback, useMemo } from 'react';
1
2
  import * as styledBase from 'styled-components';
2
3
  import styledBase__default from 'styled-components';
3
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
- import { useEffect, useState, useCallback, useMemo } from 'react';
5
5
 
6
6
  var __defProp = Object.defineProperty;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -524,6 +524,34 @@ var renderEnumSelect = (context) => {
524
524
  error && /* @__PURE__ */ jsx(ErrorMessage, { children: error })
525
525
  ] });
526
526
  };
527
+ var FormRoot = ({
528
+ children,
529
+ onSubmit
530
+ }) => {
531
+ const handleSubmit = (event) => {
532
+ event.preventDefault();
533
+ onSubmit?.();
534
+ };
535
+ return /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children });
536
+ };
537
+ var SuiteButton = ({
538
+ children,
539
+ disabled,
540
+ type,
541
+ onClick,
542
+ "data-signifier": dataSignifier
543
+ }) => {
544
+ return createElement(
545
+ "button",
546
+ {
547
+ type: type ?? "button",
548
+ disabled,
549
+ onClick: type === "submit" ? void 0 : onClick,
550
+ "data-signifier": dataSignifier
551
+ },
552
+ children
553
+ );
554
+ };
527
555
  var webSuite = {
528
556
  renderers: {
529
557
  string: renderString,
@@ -535,6 +563,13 @@ var webSuite = {
535
563
  relation_array: renderRelationArray,
536
564
  custom_single: renderCustomSingle,
537
565
  custom_array: renderCustomArray
566
+ },
567
+ primitives: {
568
+ FormRoot,
569
+ FieldWrapper,
570
+ ErrorMessage,
571
+ Label: ({ children, htmlFor }) => createElement("label", { htmlFor }, children),
572
+ Button: SuiteButton
538
573
  }
539
574
  };
540
575
  var resolvedWebSuite = resolveSuite(void 0, webSuite);
@@ -685,34 +720,43 @@ var useFormEngine = (initialValues = {}, typeInfo, options) => {
685
720
  setErrors
686
721
  };
687
722
  };
688
- var AutoField = (props) => {
689
- return webAutoField({
690
- field: props.field,
691
- fieldKey: props.fieldKey,
692
- value: props.value,
693
- onChange: props.onChange,
694
- error: props.error,
695
- disabled: props.disabled,
696
- onRelationAction: props.onRelationAction,
697
- onCustomTypeAction: props.onCustomTypeAction
698
- });
723
+ var fallbackFormRoot = ({
724
+ children,
725
+ onSubmit
726
+ }) => {
727
+ const handleSubmit = (event) => {
728
+ event.preventDefault();
729
+ onSubmit?.();
730
+ };
731
+ return /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children });
732
+ };
733
+ var fallbackButton = ({
734
+ children,
735
+ disabled,
736
+ type,
737
+ onClick
738
+ }) => {
739
+ return /* @__PURE__ */ jsx("button", { type: type ?? "button", disabled, onClick, children });
699
740
  };
700
741
  var AutoFormView = ({
701
742
  controller,
702
743
  onSubmit,
744
+ renderer,
703
745
  submitDisabled,
704
746
  onRelationAction,
705
747
  onCustomTypeAction
706
748
  }) => {
707
- const handleSubmit = (e) => {
708
- e.preventDefault();
749
+ const FormRoot2 = renderer.suite.primitives?.FormRoot ?? fallbackFormRoot;
750
+ const Button = renderer.suite.primitives?.Button ?? fallbackButton;
751
+ const AutoField2 = renderer.AutoField;
752
+ const submit = () => {
709
753
  if (controller.validate()) {
710
754
  onSubmit(controller.values);
711
755
  }
712
756
  };
713
- return /* @__PURE__ */ jsxs(FormContainer, { onSubmit: handleSubmit, children: [
757
+ return /* @__PURE__ */ jsx(FormRoot2, { onSubmit: submit, children: /* @__PURE__ */ jsxs(Fragment, { children: [
714
758
  controller.fields.filter((fieldController) => !fieldController.hidden).map((fieldController) => /* @__PURE__ */ jsx(
715
- AutoField,
759
+ AutoField2,
716
760
  {
717
761
  field: fieldController.field,
718
762
  fieldKey: fieldController.key,
@@ -725,12 +769,13 @@ var AutoFormView = ({
725
769
  },
726
770
  fieldController.key
727
771
  )),
728
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: submitDisabled, children: "Submit" })
729
- ] });
772
+ /* @__PURE__ */ jsx(Button, { type: "submit", onClick: submit, disabled: submitDisabled, children: /* @__PURE__ */ jsx(Fragment, { children: "Submit" }) })
773
+ ] }) });
730
774
  };
731
775
  var AutoForm = ({
732
776
  typeInfo,
733
777
  onSubmit,
778
+ renderer,
734
779
  initialValues,
735
780
  onValuesChange,
736
781
  onRelationAction,
@@ -749,19 +794,13 @@ var AutoForm = ({
749
794
  {
750
795
  controller,
751
796
  onSubmit,
797
+ renderer,
752
798
  onRelationAction,
753
799
  onCustomTypeAction,
754
800
  submitDisabled
755
801
  }
756
802
  );
757
803
  };
758
- var FormContainer = styled_default("form")`
759
- display: flex;
760
- flex-direction: column;
761
- align-items: flex-start;
762
- justify-content: flex-start;
763
- gap: 1em;
764
- `;
765
804
 
766
805
  // src/web/forms/createWebFormRenderer.ts
767
806
  var createWebFormRenderer = (options) => {
@@ -770,6 +809,25 @@ var createWebFormRenderer = (options) => {
770
809
  suite: options?.suite
771
810
  });
772
811
  };
812
+ var defaultWebRenderer = createWebFormRenderer();
813
+ var AutoField = (props) => {
814
+ return webAutoField({
815
+ field: props.field,
816
+ fieldKey: props.fieldKey,
817
+ value: props.value,
818
+ onChange: props.onChange,
819
+ error: props.error,
820
+ disabled: props.disabled,
821
+ onRelationAction: props.onRelationAction,
822
+ onCustomTypeAction: props.onCustomTypeAction
823
+ });
824
+ };
825
+ var AutoFormView2 = (props) => {
826
+ return /* @__PURE__ */ jsx(AutoFormView, { ...props, renderer: defaultWebRenderer });
827
+ };
828
+ var AutoForm2 = (props) => {
829
+ return /* @__PURE__ */ jsx(AutoForm, { ...props, renderer: defaultWebRenderer });
830
+ };
773
831
 
774
832
  // src/app/utils/easy-layout/parseTemplate.ts
775
833
  var parseTrackSpec = (token) => {
@@ -1018,4 +1076,4 @@ var getEasyLayout = (extendFrom, areasExtendFrom, options = {}) => {
1018
1076
  return createEasyLayout(styledFactory, extendFrom, areasExtendFrom, options);
1019
1077
  };
1020
1078
 
1021
- export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm, AutoFormView, ErrorMessage, FieldWrapper, createWebFormRenderer, getEasyLayout, webAutoField, webSuite };
1079
+ export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, ErrorMessage, FieldWrapper, createWebFormRenderer, getEasyLayout, webAutoField, webSuite };