@faasjs/react 3.6.0 → 3.7.0-beta.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.
package/README.md CHANGED
@@ -22,6 +22,7 @@ React plugin for FaasJS.
22
22
  - [useEqualCallback](functions/useEqualCallback.md): Memoize a callback with deep equality.
23
23
  - [useConstant](functions/useConstant.md): Create a constant value with hooks.
24
24
  - [createSplittingContext](functions/createSplittingContext.md): Create a context for code splitting.
25
+ - [splittingState](functions/splittingState.md): Create a splitting states.
25
26
  - [OptionalWrapper](functions/OptionalWrapper.md): Render a component optionally.
26
27
  - [ErrorBoundary](classes/ErrorBoundary.md): Catch errors in the component tree.
27
28
  - Fetch Data:
@@ -43,6 +44,7 @@ npm install @faasjs/react
43
44
  - [faas](functions/faas.md)
44
45
  - [FaasDataWrapper](functions/FaasDataWrapper.md)
45
46
  - [FaasReactClient](functions/FaasReactClient.md)
47
+ - [Form](functions/Form.md)
46
48
  - [getClient](functions/getClient.md)
47
49
  - [OptionalWrapper](functions/OptionalWrapper.md)
48
50
  - [useConstant](functions/useConstant.md)
@@ -51,6 +53,7 @@ npm install @faasjs/react
51
53
  - [useEqualMemo](functions/useEqualMemo.md)
52
54
  - [useEqualMemoize](functions/useEqualMemoize.md)
53
55
  - [useFaas](functions/useFaas.md)
56
+ - [useSplittingState](functions/useSplittingState.md)
54
57
  - [withFaasData](functions/withFaasData.md)
55
58
 
56
59
  ## Classes
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { FaasAction, FaasData, FaasParams } from '@faasjs/types';
2
2
  export { FaasAction, FaasData, FaasParams } from '@faasjs/types';
3
3
  import { Response, BaseUrl, ResponseError, Options, FaasBrowserClient } from '@faasjs/browser';
4
4
  export { Options, Response, ResponseError, ResponseHeaders } from '@faasjs/browser';
5
- import { ReactNode, ReactElement, Component, ComponentType, ComponentProps } from 'react';
5
+ import { ReactNode, Dispatch, SetStateAction, ReactElement, Component, ComponentType, ComponentProps, JSXElementConstructor } from 'react';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
 
8
8
  /**
@@ -105,21 +105,75 @@ declare function createSplittingContext<T extends Record<string, any>>(defaultVa
105
105
  } | (keyof T)[]): {
106
106
  /**
107
107
  * The provider component of the splitting context.
108
+ *
108
109
  * @see https://faasjs.com/doc/react/functions/createSplittingContext.html#provider
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * function App() {
114
+ * const [value, setValue] = useState(0)
115
+ *
116
+ * return (
117
+ * <Provider value={{ value, setValue }}>
118
+ * <ReaderComponent />
119
+ * <WriterComponent />
120
+ * </Provider>
121
+ * )
122
+ * }
123
+ * ```
109
124
  */
110
125
  Provider<NewT extends T = T>(props: {
111
126
  value?: NewT;
112
127
  children: ReactNode;
128
+ /**
129
+ * Whether to use memoization for the children.
130
+ *
131
+ * @default false
132
+ *
133
+ * `true`: memoize the children without dependencies.
134
+ * `any[]`: memoize the children with specific dependencies.
135
+ */
113
136
  memo?: true | any[];
114
137
  }): ReactNode;
115
138
  /**
116
139
  * The hook to use the splitting context.
117
140
  *
118
141
  * @see https://faasjs.com/doc/react/functions/createSplittingContext.html#use
142
+ *
143
+ * @example
144
+ * ```tsx
145
+ * function ChildComponent() {
146
+ * const { value, setValue } = use()
147
+ *
148
+ * return <div>{value}<button onClick={() => setValue(1)}>change value</button></div>
149
+ * }
150
+ * ```
119
151
  */
120
152
  use: <NewT extends T = T>() => Readonly<NewT>;
121
153
  };
122
154
 
155
+ type SetPrefix<S extends string | number | symbol> = S extends string ? (S extends `${infer First}${infer Rest}` ? `set${Capitalize<First>}${Rest}` : never) : never;
156
+ type StateSetters<T> = {
157
+ [K in keyof T as SetPrefix<K>]: Dispatch<SetStateAction<T[K]>>;
158
+ };
159
+ type StatesWithSetters<T> = T & StateSetters<T>;
160
+ /**
161
+ * A hook that initializes and splits state variables and their corresponding setters.
162
+ *
163
+ * @template T - A generic type that extends a record with string keys and any values.
164
+ * @param {T} initialStates - An object containing the initial states.
165
+ *
166
+ * @example
167
+ * ```tsx
168
+ * function Counter() {
169
+ * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' });
170
+ *
171
+ * return <>{name}: {count}</>
172
+ * }
173
+ * ```
174
+ */
175
+ declare function useSplittingState<T extends Record<string, unknown>>(initialStates: T): StatesWithSetters<T>;
176
+
123
177
  /**
124
178
  * Injects FaasData props.
125
179
  */
@@ -317,4 +371,64 @@ declare const OptionalWrapper: React.FC<OptionalWrapperProps> & {
317
371
  whyDidYouRender: boolean;
318
372
  };
319
373
 
320
- export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, type useFaasOptions, withFaasData };
374
+ type FormRules = {
375
+ type?: 'string' | 'number';
376
+ required?: boolean;
377
+ };
378
+
379
+ type FormInputComponentProps = {
380
+ name: string;
381
+ value: any;
382
+ onChange: (value: any) => void;
383
+ };
384
+ type FormInputComponent<Extra extends Record<string, any> = Record<string, any>> = ComponentType<FormInputComponentProps & Extra>;
385
+ type InferFormInputProps<T extends FormInputComponent | JSXElementConstructor<any>> = T extends FormInputComponent ? Omit<ComponentProps<T>, 'name' | 'value' | 'onChange'> : Omit<ComponentProps<T>, 'name' | 'value'>;
386
+ type FormInputProps<FormElements extends FormElementTypes = FormElementTypes> = {
387
+ Input: FormInputComponent;
388
+ } | {
389
+ type?: 'input';
390
+ props?: InferFormInputProps<FormElements['input']>;
391
+ } | {
392
+ type: 'select';
393
+ props?: InferFormInputProps<FormElements['select']>;
394
+ };
395
+
396
+ type FormLabelProps<FormElements extends FormElementTypes = FormElementTypes> = {
397
+ name: string;
398
+ rules?: FormRules;
399
+ label?: {
400
+ title?: ReactNode;
401
+ description?: ReactNode;
402
+ Label?: React.ComponentType<FormLabelProps>;
403
+ };
404
+ input?: FormInputProps<FormElements>;
405
+ };
406
+
407
+ type FormButtonProps = {
408
+ children?: React.ReactNode;
409
+ disabled?: boolean;
410
+ onClick?: () => void;
411
+ };
412
+
413
+ type FormSelectOption = {
414
+ value: string | number;
415
+ label: string;
416
+ };
417
+ type FormElementTypes = {
418
+ label: ComponentType<FormLabelProps>;
419
+ input: FormInputComponent;
420
+ select: FormInputComponent<{
421
+ options?: FormSelectOption[];
422
+ }>;
423
+ button: ComponentType<FormButtonProps>;
424
+ };
425
+
426
+ type FormProps<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes> = {
427
+ items: FormLabelProps<FormElements>[];
428
+ onSubmit?: (values: Values) => Promise<void>;
429
+ elements?: FormElements;
430
+ defaultValues?: Values;
431
+ };
432
+ declare function FormContainer<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes>({ defaultValues, elements, ...props }: FormProps<Values, FormElements>): react_jsx_runtime.JSX.Element;
433
+
434
+ export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, FormContainer as Form, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, type useFaasOptions, useSplittingState, withFaasData };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { FaasAction, FaasData, FaasParams } from '@faasjs/types';
2
2
  export { FaasAction, FaasData, FaasParams } from '@faasjs/types';
3
3
  import { Response, BaseUrl, ResponseError, Options, FaasBrowserClient } from '@faasjs/browser';
4
4
  export { Options, Response, ResponseError, ResponseHeaders } from '@faasjs/browser';
5
- import { ReactNode, ReactElement, Component, ComponentType, ComponentProps } from 'react';
5
+ import { ReactNode, Dispatch, SetStateAction, ReactElement, Component, ComponentType, ComponentProps, JSXElementConstructor } from 'react';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
 
8
8
  /**
@@ -105,21 +105,75 @@ declare function createSplittingContext<T extends Record<string, any>>(defaultVa
105
105
  } | (keyof T)[]): {
106
106
  /**
107
107
  * The provider component of the splitting context.
108
+ *
108
109
  * @see https://faasjs.com/doc/react/functions/createSplittingContext.html#provider
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * function App() {
114
+ * const [value, setValue] = useState(0)
115
+ *
116
+ * return (
117
+ * <Provider value={{ value, setValue }}>
118
+ * <ReaderComponent />
119
+ * <WriterComponent />
120
+ * </Provider>
121
+ * )
122
+ * }
123
+ * ```
109
124
  */
110
125
  Provider<NewT extends T = T>(props: {
111
126
  value?: NewT;
112
127
  children: ReactNode;
128
+ /**
129
+ * Whether to use memoization for the children.
130
+ *
131
+ * @default false
132
+ *
133
+ * `true`: memoize the children without dependencies.
134
+ * `any[]`: memoize the children with specific dependencies.
135
+ */
113
136
  memo?: true | any[];
114
137
  }): ReactNode;
115
138
  /**
116
139
  * The hook to use the splitting context.
117
140
  *
118
141
  * @see https://faasjs.com/doc/react/functions/createSplittingContext.html#use
142
+ *
143
+ * @example
144
+ * ```tsx
145
+ * function ChildComponent() {
146
+ * const { value, setValue } = use()
147
+ *
148
+ * return <div>{value}<button onClick={() => setValue(1)}>change value</button></div>
149
+ * }
150
+ * ```
119
151
  */
120
152
  use: <NewT extends T = T>() => Readonly<NewT>;
121
153
  };
122
154
 
155
+ type SetPrefix<S extends string | number | symbol> = S extends string ? (S extends `${infer First}${infer Rest}` ? `set${Capitalize<First>}${Rest}` : never) : never;
156
+ type StateSetters<T> = {
157
+ [K in keyof T as SetPrefix<K>]: Dispatch<SetStateAction<T[K]>>;
158
+ };
159
+ type StatesWithSetters<T> = T & StateSetters<T>;
160
+ /**
161
+ * A hook that initializes and splits state variables and their corresponding setters.
162
+ *
163
+ * @template T - A generic type that extends a record with string keys and any values.
164
+ * @param {T} initialStates - An object containing the initial states.
165
+ *
166
+ * @example
167
+ * ```tsx
168
+ * function Counter() {
169
+ * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' });
170
+ *
171
+ * return <>{name}: {count}</>
172
+ * }
173
+ * ```
174
+ */
175
+ declare function useSplittingState<T extends Record<string, unknown>>(initialStates: T): StatesWithSetters<T>;
176
+
123
177
  /**
124
178
  * Injects FaasData props.
125
179
  */
@@ -317,4 +371,64 @@ declare const OptionalWrapper: React.FC<OptionalWrapperProps> & {
317
371
  whyDidYouRender: boolean;
318
372
  };
319
373
 
320
- export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, type useFaasOptions, withFaasData };
374
+ type FormRules = {
375
+ type?: 'string' | 'number';
376
+ required?: boolean;
377
+ };
378
+
379
+ type FormInputComponentProps = {
380
+ name: string;
381
+ value: any;
382
+ onChange: (value: any) => void;
383
+ };
384
+ type FormInputComponent<Extra extends Record<string, any> = Record<string, any>> = ComponentType<FormInputComponentProps & Extra>;
385
+ type InferFormInputProps<T extends FormInputComponent | JSXElementConstructor<any>> = T extends FormInputComponent ? Omit<ComponentProps<T>, 'name' | 'value' | 'onChange'> : Omit<ComponentProps<T>, 'name' | 'value'>;
386
+ type FormInputProps<FormElements extends FormElementTypes = FormElementTypes> = {
387
+ Input: FormInputComponent;
388
+ } | {
389
+ type?: 'input';
390
+ props?: InferFormInputProps<FormElements['input']>;
391
+ } | {
392
+ type: 'select';
393
+ props?: InferFormInputProps<FormElements['select']>;
394
+ };
395
+
396
+ type FormLabelProps<FormElements extends FormElementTypes = FormElementTypes> = {
397
+ name: string;
398
+ rules?: FormRules;
399
+ label?: {
400
+ title?: ReactNode;
401
+ description?: ReactNode;
402
+ Label?: React.ComponentType<FormLabelProps>;
403
+ };
404
+ input?: FormInputProps<FormElements>;
405
+ };
406
+
407
+ type FormButtonProps = {
408
+ children?: React.ReactNode;
409
+ disabled?: boolean;
410
+ onClick?: () => void;
411
+ };
412
+
413
+ type FormSelectOption = {
414
+ value: string | number;
415
+ label: string;
416
+ };
417
+ type FormElementTypes = {
418
+ label: ComponentType<FormLabelProps>;
419
+ input: FormInputComponent;
420
+ select: FormInputComponent<{
421
+ options?: FormSelectOption[];
422
+ }>;
423
+ button: ComponentType<FormButtonProps>;
424
+ };
425
+
426
+ type FormProps<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes> = {
427
+ items: FormLabelProps<FormElements>[];
428
+ onSubmit?: (values: Values) => Promise<void>;
429
+ elements?: FormElements;
430
+ defaultValues?: Values;
431
+ };
432
+ declare function FormContainer<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes>({ defaultValues, elements, ...props }: FormProps<Values, FormElements>): react_jsx_runtime.JSX.Element;
433
+
434
+ export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, FormContainer as Form, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, type useFaasOptions, useSplittingState, withFaasData };
package/dist/index.js CHANGED
@@ -115,6 +115,14 @@ function createSplittingContext(defaultValue) {
115
115
  use
116
116
  };
117
117
  }
118
+ function useSplittingState(initialStates) {
119
+ const states = {};
120
+ for (const key of Object.keys(initialStates)) {
121
+ const state = react.useState(initialStates[key]);
122
+ Object.assign(states, { [key]: state[0], [`set${String(key).charAt(0).toUpperCase()}${String(key).slice(1)}`]: state[1] });
123
+ }
124
+ return states;
125
+ }
118
126
  function FaasDataWrapper(props) {
119
127
  const request = getClient(props.baseUrl).useFaas(
120
128
  props.action,
@@ -326,9 +334,152 @@ var OptionalWrapper = ({ condition, Wrapper, wrapperProps, children }) => {
326
334
  };
327
335
  OptionalWrapper.whyDidYouRender = true;
328
336
 
337
+ // src/Form/context.tsx
338
+ var FormContext = createSplittingContext(["items", "onSubmit", "elements", "submitting", "setSubmitting", "values", "setValues"]);
339
+ var FormContextProvider = FormContext.Provider;
340
+ var useFormContext = FormContext.use;
341
+ function FormLabel(props) {
342
+ const { elements } = useFormContext();
343
+ if (props.label?.Label) return /* @__PURE__ */ jsxRuntime.jsx(props.label.Label, { ...props });
344
+ return /* @__PURE__ */ jsxRuntime.jsx(elements.label, { ...props });
345
+ }
346
+ function FormBody() {
347
+ const { items } = useFormContext();
348
+ return items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(FormLabel, { ...item }, item.name));
349
+ }
350
+ function processValue(input, rules) {
351
+ let value = input;
352
+ if (typeof input === "object" && "target" in input) {
353
+ value = input.target.value;
354
+ }
355
+ switch (rules?.type) {
356
+ case "number":
357
+ return Number(value);
358
+ case "string":
359
+ return String(value);
360
+ default:
361
+ return value;
362
+ }
363
+ }
364
+ function FormInput({
365
+ name,
366
+ rules,
367
+ ...rest
368
+ }) {
369
+ const { elements, values, setValues } = useFormContext();
370
+ const value = values?.[name];
371
+ if ("Input" in rest && rest.Input) {
372
+ return /* @__PURE__ */ jsxRuntime.jsx(
373
+ rest.Input,
374
+ {
375
+ name,
376
+ value,
377
+ onChange: (v) => setValues((prev) => ({
378
+ ...prev,
379
+ [name]: v
380
+ }))
381
+ }
382
+ );
383
+ }
384
+ if ("type" in rest || "props" in rest) {
385
+ switch (rest.type) {
386
+ case "select":
387
+ return /* @__PURE__ */ jsxRuntime.jsx(
388
+ elements.select,
389
+ {
390
+ name,
391
+ value,
392
+ onChange: (v) => setValues((prev) => ({
393
+ ...prev,
394
+ [name]: processValue(v, rules)
395
+ })),
396
+ ...rest.props
397
+ }
398
+ );
399
+ default:
400
+ return /* @__PURE__ */ jsxRuntime.jsx(
401
+ elements.input,
402
+ {
403
+ name,
404
+ value,
405
+ onChange: (v) => setValues((prev) => ({
406
+ ...prev,
407
+ [name]: processValue(v, rules)
408
+ })),
409
+ ...rest.props
410
+ }
411
+ );
412
+ }
413
+ }
414
+ return /* @__PURE__ */ jsxRuntime.jsx(
415
+ elements.input,
416
+ {
417
+ name,
418
+ value,
419
+ onChange: (v) => setValues((prev) => ({
420
+ ...prev,
421
+ [name]: processValue(v, rules)
422
+ }))
423
+ }
424
+ );
425
+ }
426
+ var FormElements = {
427
+ label: react.forwardRef(({ name, label, input, rules }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("label", { ref, children: [
428
+ label?.title ?? name,
429
+ /* @__PURE__ */ jsxRuntime.jsx(FormInput, { name, rules, ...input }),
430
+ label?.description
431
+ ] })),
432
+ input: react.forwardRef(
433
+ (props, ref) => /* @__PURE__ */ jsxRuntime.jsx("input", { ...props, ref })
434
+ ),
435
+ select: react.forwardRef(({ options, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("select", { ...props, ref, children: options?.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value)) })),
436
+ button: react.forwardRef(({ disabled, children, onClick, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled, onClick, ...props, ref, children }))
437
+ };
438
+ function FormFooter() {
439
+ const { submitting, setSubmitting, onSubmit, values, elements } = useFormContext();
440
+ return /* @__PURE__ */ jsxRuntime.jsx(
441
+ elements.button,
442
+ {
443
+ disabled: submitting,
444
+ onClick: () => {
445
+ setSubmitting(true);
446
+ onSubmit(values).finally(() => setSubmitting(false));
447
+ },
448
+ children: "Submit"
449
+ }
450
+ );
451
+ }
452
+ function mergeValues(items, defaultValues = {}) {
453
+ const values = {};
454
+ for (const item of items)
455
+ values[item.name] = defaultValues[item.name] ?? "";
456
+ return values;
457
+ }
458
+ function FormContainer({ defaultValues, elements, ...props }) {
459
+ const states = useSplittingState({
460
+ values: mergeValues(props.items, defaultValues),
461
+ submitting: false,
462
+ elements: Object.assign(FormElements, elements)
463
+ });
464
+ return /* @__PURE__ */ jsxRuntime.jsxs(
465
+ FormContextProvider,
466
+ {
467
+ value: {
468
+ ...states,
469
+ ...props
470
+ },
471
+ children: [
472
+ /* @__PURE__ */ jsxRuntime.jsx(FormBody, {}),
473
+ /* @__PURE__ */ jsxRuntime.jsx(FormFooter, {})
474
+ ]
475
+ }
476
+ );
477
+ }
478
+
329
479
  exports.ErrorBoundary = ErrorBoundary;
330
480
  exports.FaasDataWrapper = FaasDataWrapper;
331
481
  exports.FaasReactClient = FaasReactClient;
482
+ exports.Form = FormContainer;
332
483
  exports.OptionalWrapper = OptionalWrapper;
333
484
  exports.createSplittingContext = createSplittingContext;
334
485
  exports.equal = equal;
@@ -340,4 +491,5 @@ exports.useEqualEffect = useEqualEffect;
340
491
  exports.useEqualMemo = useEqualMemo;
341
492
  exports.useEqualMemoize = useEqualMemoize;
342
493
  exports.useFaas = useFaas;
494
+ exports.useSplittingState = useSplittingState;
343
495
  exports.withFaasData = withFaasData;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { useRef, useMemo, useEffect, useCallback, createContext, useState, cloneElement, Component, useContext } from 'react';
2
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
1
+ import { forwardRef, useRef, useMemo, useEffect, useCallback, createContext, useState, cloneElement, Component, useContext } from 'react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { FaasBrowserClient } from '@faasjs/browser';
4
4
 
5
5
  // src/constant.ts
@@ -113,6 +113,14 @@ function createSplittingContext(defaultValue) {
113
113
  use
114
114
  };
115
115
  }
116
+ function useSplittingState(initialStates) {
117
+ const states = {};
118
+ for (const key of Object.keys(initialStates)) {
119
+ const state = useState(initialStates[key]);
120
+ Object.assign(states, { [key]: state[0], [`set${String(key).charAt(0).toUpperCase()}${String(key).slice(1)}`]: state[1] });
121
+ }
122
+ return states;
123
+ }
116
124
  function FaasDataWrapper(props) {
117
125
  const request = getClient(props.baseUrl).useFaas(
118
126
  props.action,
@@ -324,4 +332,146 @@ var OptionalWrapper = ({ condition, Wrapper, wrapperProps, children }) => {
324
332
  };
325
333
  OptionalWrapper.whyDidYouRender = true;
326
334
 
327
- export { ErrorBoundary, FaasDataWrapper, FaasReactClient, OptionalWrapper, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, withFaasData };
335
+ // src/Form/context.tsx
336
+ var FormContext = createSplittingContext(["items", "onSubmit", "elements", "submitting", "setSubmitting", "values", "setValues"]);
337
+ var FormContextProvider = FormContext.Provider;
338
+ var useFormContext = FormContext.use;
339
+ function FormLabel(props) {
340
+ const { elements } = useFormContext();
341
+ if (props.label?.Label) return /* @__PURE__ */ jsx(props.label.Label, { ...props });
342
+ return /* @__PURE__ */ jsx(elements.label, { ...props });
343
+ }
344
+ function FormBody() {
345
+ const { items } = useFormContext();
346
+ return items.map((item) => /* @__PURE__ */ jsx(FormLabel, { ...item }, item.name));
347
+ }
348
+ function processValue(input, rules) {
349
+ let value = input;
350
+ if (typeof input === "object" && "target" in input) {
351
+ value = input.target.value;
352
+ }
353
+ switch (rules?.type) {
354
+ case "number":
355
+ return Number(value);
356
+ case "string":
357
+ return String(value);
358
+ default:
359
+ return value;
360
+ }
361
+ }
362
+ function FormInput({
363
+ name,
364
+ rules,
365
+ ...rest
366
+ }) {
367
+ const { elements, values, setValues } = useFormContext();
368
+ const value = values?.[name];
369
+ if ("Input" in rest && rest.Input) {
370
+ return /* @__PURE__ */ jsx(
371
+ rest.Input,
372
+ {
373
+ name,
374
+ value,
375
+ onChange: (v) => setValues((prev) => ({
376
+ ...prev,
377
+ [name]: v
378
+ }))
379
+ }
380
+ );
381
+ }
382
+ if ("type" in rest || "props" in rest) {
383
+ switch (rest.type) {
384
+ case "select":
385
+ return /* @__PURE__ */ jsx(
386
+ elements.select,
387
+ {
388
+ name,
389
+ value,
390
+ onChange: (v) => setValues((prev) => ({
391
+ ...prev,
392
+ [name]: processValue(v, rules)
393
+ })),
394
+ ...rest.props
395
+ }
396
+ );
397
+ default:
398
+ return /* @__PURE__ */ jsx(
399
+ elements.input,
400
+ {
401
+ name,
402
+ value,
403
+ onChange: (v) => setValues((prev) => ({
404
+ ...prev,
405
+ [name]: processValue(v, rules)
406
+ })),
407
+ ...rest.props
408
+ }
409
+ );
410
+ }
411
+ }
412
+ return /* @__PURE__ */ jsx(
413
+ elements.input,
414
+ {
415
+ name,
416
+ value,
417
+ onChange: (v) => setValues((prev) => ({
418
+ ...prev,
419
+ [name]: processValue(v, rules)
420
+ }))
421
+ }
422
+ );
423
+ }
424
+ var FormElements = {
425
+ label: forwardRef(({ name, label, input, rules }, ref) => /* @__PURE__ */ jsxs("label", { ref, children: [
426
+ label?.title ?? name,
427
+ /* @__PURE__ */ jsx(FormInput, { name, rules, ...input }),
428
+ label?.description
429
+ ] })),
430
+ input: forwardRef(
431
+ (props, ref) => /* @__PURE__ */ jsx("input", { ...props, ref })
432
+ ),
433
+ select: forwardRef(({ options, ...props }, ref) => /* @__PURE__ */ jsx("select", { ...props, ref, children: options?.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value)) })),
434
+ button: forwardRef(({ disabled, children, onClick, ...props }, ref) => /* @__PURE__ */ jsx("button", { type: "button", disabled, onClick, ...props, ref, children }))
435
+ };
436
+ function FormFooter() {
437
+ const { submitting, setSubmitting, onSubmit, values, elements } = useFormContext();
438
+ return /* @__PURE__ */ jsx(
439
+ elements.button,
440
+ {
441
+ disabled: submitting,
442
+ onClick: () => {
443
+ setSubmitting(true);
444
+ onSubmit(values).finally(() => setSubmitting(false));
445
+ },
446
+ children: "Submit"
447
+ }
448
+ );
449
+ }
450
+ function mergeValues(items, defaultValues = {}) {
451
+ const values = {};
452
+ for (const item of items)
453
+ values[item.name] = defaultValues[item.name] ?? "";
454
+ return values;
455
+ }
456
+ function FormContainer({ defaultValues, elements, ...props }) {
457
+ const states = useSplittingState({
458
+ values: mergeValues(props.items, defaultValues),
459
+ submitting: false,
460
+ elements: Object.assign(FormElements, elements)
461
+ });
462
+ return /* @__PURE__ */ jsxs(
463
+ FormContextProvider,
464
+ {
465
+ value: {
466
+ ...states,
467
+ ...props
468
+ },
469
+ children: [
470
+ /* @__PURE__ */ jsx(FormBody, {}),
471
+ /* @__PURE__ */ jsx(FormFooter, {})
472
+ ]
473
+ }
474
+ );
475
+ }
476
+
477
+ export { ErrorBoundary, FaasDataWrapper, FaasReactClient, FormContainer as Form, OptionalWrapper, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useSplittingState, withFaasData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "3.6.0",
3
+ "version": "3.7.0-beta.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -28,19 +28,18 @@
28
28
  },
29
29
  "funding": "https://github.com/sponsors/faasjs",
30
30
  "scripts": {
31
- "build": "tsup src/index.tsx --config ../../tsup.config.json"
31
+ "build": "tsup src/index.tsx --config ../../tsup.config.json --external react"
32
32
  },
33
33
  "files": [
34
34
  "dist"
35
35
  ],
36
36
  "peerDependencies": {
37
- "react": "*",
38
- "@faasjs/browser": "3.6.0"
37
+ "@faasjs/browser": "3.7.0-beta.0"
39
38
  },
40
39
  "devDependencies": {
40
+ "@faasjs/browser": "3.7.0-beta.0",
41
41
  "@types/react": "*",
42
- "react": "*",
43
- "@faasjs/browser": "3.6.0"
42
+ "react": "*"
44
43
  },
45
44
  "engines": {
46
45
  "node": ">=22.0.0",