@faasjs/react 8.0.0-beta.16 → 8.0.0-beta.17

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
@@ -6,9 +6,6 @@
6
6
  - [equal](functions/equal.md)
7
7
  - [faas](functions/faas.md)
8
8
  - [FaasReactClient](functions/FaasReactClient.md)
9
- - [Form](functions/Form.md)
10
- - [FormInput](functions/FormInput.md)
11
- - [FormItem](functions/FormItem.md)
12
9
  - [generateId](functions/generateId.md)
13
10
  - [getClient](functions/getClient.md)
14
11
  - [OptionalWrapper](functions/OptionalWrapper.md)
@@ -23,7 +20,6 @@
23
20
  - [usePrevious](functions/usePrevious.md)
24
21
  - [useSplittingState](functions/useSplittingState.md)
25
22
  - [useStateRef](functions/useStateRef.md)
26
- - [validValues](functions/validValues.md)
27
23
  - [withFaasData](functions/withFaasData.md)
28
24
 
29
25
  ## Classes
@@ -51,22 +47,6 @@
51
47
  - [FaasParams](type-aliases/FaasParams.md)
52
48
  - [FaasReactClientInstance](type-aliases/FaasReactClientInstance.md)
53
49
  - [FaasReactClientOptions](type-aliases/FaasReactClientOptions.md)
54
- - [FormButtonElementProps](type-aliases/FormButtonElementProps.md)
55
- - [FormContextProps](type-aliases/FormContextProps.md)
56
- - [FormDefaultRulesOptions](type-aliases/FormDefaultRulesOptions.md)
57
- - [FormElementTypes](type-aliases/FormElementTypes.md)
58
- - [FormInputElementProps](type-aliases/FormInputElementProps.md)
59
- - [FormInputProps](type-aliases/FormInputProps.md)
60
- - [FormItemName](type-aliases/FormItemName.md)
61
- - [FormItemProps](type-aliases/FormItemProps.md)
62
- - [FormLabelElementProps](type-aliases/FormLabelElementProps.md)
63
- - [FormLang](type-aliases/FormLang.md)
64
- - [FormProps](type-aliases/FormProps.md)
65
- - [FormRule](type-aliases/FormRule.md)
66
- - [FormRules](type-aliases/FormRules.md)
67
- - [InferFormInputProps](type-aliases/InferFormInputProps.md)
68
- - [InferFormRulesOptions](type-aliases/InferFormRulesOptions.md)
69
- - [InferRuleOption](type-aliases/InferRuleOption.md)
70
50
  - [MockHandler](type-aliases/MockHandler.md)
71
51
  - [OnError](type-aliases/OnError.md)
72
52
  - [OptionalWrapperProps](type-aliases/OptionalWrapperProps.md)
@@ -83,8 +63,3 @@
83
63
  ## Variables
84
64
 
85
65
  - [FaasDataWrapper](variables/FaasDataWrapper.md)
86
- - [FormContextProvider](variables/FormContextProvider.md)
87
- - [FormDefaultElements](variables/FormDefaultElements.md)
88
- - [FormDefaultLang](variables/FormDefaultLang.md)
89
- - [FormDefaultRules](variables/FormDefaultRules.md)
90
- - [useFormContext](variables/useFormContext.md)
package/dist/index.cjs CHANGED
@@ -1080,43 +1080,33 @@ var ErrorBoundary = class extends react.Component {
1080
1080
  }
1081
1081
  };
1082
1082
  //#endregion
1083
- //#region src/useStateRef.ts
1083
+ //#region src/OptionalWrapper.tsx
1084
1084
  /**
1085
- * Custom hook that returns a stateful value and a ref to that value.
1086
- *
1087
- * @template T - The type of the value.
1088
- * @param initialValue - Initial state value. When omitted, state starts as `null`.
1089
- * @returns Tuple containing the current state, the state setter, and a ref that always points at the latest state.
1085
+ * A wrapper component that conditionally wraps its children with a provided wrapper component.
1090
1086
  *
1091
1087
  * @example
1092
1088
  * ```tsx
1093
- * import { useStateRef } from '@faasjs/react'
1089
+ * import { OptionalWrapper } from '@faasjs/react'
1094
1090
  *
1095
- * function MyComponent() {
1096
- * const [value, setValue, ref] = useStateRef(0)
1091
+ * const Wrapper = ({ children }: { children: React.ReactNode }) => (
1092
+ * <div className='wrapper'>{children}</div>
1093
+ * )
1097
1094
  *
1098
- * return (
1099
- * <div>
1100
- * <p>Value: {value}</p>
1101
- * <button onClick={() => setValue(value + 1)}>Increment</button>
1102
- * <button onClick={() => console.log(ref.current)}>Submit</button>
1103
- * </div>
1104
- * )
1105
- * }
1095
+ * const App = () => (
1096
+ * <OptionalWrapper condition={true} Wrapper={Wrapper}>
1097
+ * <span>Test</span>
1098
+ * </OptionalWrapper>
1099
+ * )
1106
1100
  * ```
1107
1101
  */
1108
- function useStateRef(initialValue) {
1109
- const [state, setState] = (0, react.useState)(initialValue ?? null);
1110
- const ref = (0, react.useRef)(state);
1111
- (0, react.useEffect)(() => {
1112
- ref.current = state;
1113
- }, [state]);
1114
- return [
1115
- state,
1116
- setState,
1117
- ref
1118
- ];
1102
+ function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
1103
+ if (condition) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, {
1104
+ ...wrapperProps,
1105
+ children
1106
+ });
1107
+ return children;
1119
1108
  }
1109
+ OptionalWrapper.displayName = "OptionalWrapper";
1120
1110
  //#endregion
1121
1111
  //#region src/splittingState.tsx
1122
1112
  /**
@@ -1229,278 +1219,6 @@ function createSplittingContext(defaultValue) {
1229
1219
  };
1230
1220
  }
1231
1221
  //#endregion
1232
- //#region src/Form/context.tsx
1233
- const FormContext = createSplittingContext([
1234
- "items",
1235
- "onSubmit",
1236
- "Elements",
1237
- "lang",
1238
- "rules",
1239
- "submitting",
1240
- "setSubmitting",
1241
- "values",
1242
- "setValues",
1243
- "errors",
1244
- "setErrors",
1245
- "valuesRef"
1246
- ]);
1247
- const FormContextProvider = FormContext.Provider;
1248
- const useFormContext = FormContext.use;
1249
- //#endregion
1250
- //#region src/Form/Input.tsx
1251
- function processValue(input, rules) {
1252
- switch (rules?.type) {
1253
- case "number": return Number(input);
1254
- case "string": return String(input);
1255
- default: return input;
1256
- }
1257
- }
1258
- function FormInput({ name, rules, ...rest }) {
1259
- const { Elements, values, setValues } = useFormContext();
1260
- const value = values?.[name];
1261
- if (rest.Input) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(rest.Input, {
1262
- name,
1263
- value,
1264
- onChange: (v) => setValues((prev) => ({
1265
- ...prev,
1266
- [name]: processValue(v, rules)
1267
- })),
1268
- ...rest.props
1269
- });
1270
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Elements.Input, {
1271
- name,
1272
- value,
1273
- onChange: (v) => setValues((prev) => ({
1274
- ...prev,
1275
- [name]: processValue(v, rules)
1276
- })),
1277
- ...rest.props
1278
- });
1279
- }
1280
- FormInput.displayName = "FormInput";
1281
- //#endregion
1282
- //#region src/Form/Item.tsx
1283
- function FormItem(props) {
1284
- const { Elements, errors } = useFormContext();
1285
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(props.label?.Label ?? Elements.Label, {
1286
- name: props.name,
1287
- ...props.label,
1288
- error: errors[props.name],
1289
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormInput, {
1290
- name: props.name,
1291
- ...props.input,
1292
- ...props.rules ? { rules: props.rules } : {}
1293
- })
1294
- });
1295
- }
1296
- FormItem.displayName = "FormItem";
1297
- //#endregion
1298
- //#region src/Form/Body.tsx
1299
- function FormBody() {
1300
- const { items } = useFormContext();
1301
- return items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormItem, { ...item }, item.name));
1302
- }
1303
- FormBody.displayName = "FormBody";
1304
- //#endregion
1305
- //#region src/Form/elements/Button.tsx
1306
- const FormButtonElement = (0, react.forwardRef)(({ children, submit, submitting, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1307
- type: "button",
1308
- disabled: submitting,
1309
- onClick: submit,
1310
- ...props,
1311
- ref,
1312
- children
1313
- }));
1314
- FormButtonElement.displayName = "FormButtonElement";
1315
- //#endregion
1316
- //#region src/Form/elements/Input.tsx
1317
- const FormInputElement = (0, react.forwardRef)(({ onChange, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1318
- ...props,
1319
- onChange: (e) => onChange(e.target.value),
1320
- ref
1321
- }));
1322
- FormInputElement.displayName = "FormInputElement";
1323
- //#endregion
1324
- //#region src/Form/elements/Label.tsx
1325
- const FormLabelElement = ({ name, title, description, error, children }) => {
1326
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
1327
- title ?? name,
1328
- children,
1329
- description,
1330
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1331
- style: { color: "red" },
1332
- children: error.message
1333
- })
1334
- ] });
1335
- };
1336
- FormLabelElement.displayName = "FormLabelElement";
1337
- //#endregion
1338
- //#region src/Form/elements/index.ts
1339
- const FormDefaultElements = {
1340
- Label: FormLabelElement,
1341
- Input: FormInputElement,
1342
- Button: FormButtonElement
1343
- };
1344
- //#endregion
1345
- //#region src/Form/rules.ts
1346
- /**
1347
- * Default validation rules for a form.
1348
- */
1349
- const FormDefaultRules = {
1350
- required: async (value, _, lang) => {
1351
- if (value === null || value === void 0 || value === "" || Number.isNaN(value)) throw Error(lang?.required);
1352
- },
1353
- type: async (value, options, lang) => {
1354
- switch (options) {
1355
- case "string":
1356
- if (typeof value !== "string") throw Error(lang?.string);
1357
- break;
1358
- case "number":
1359
- if (Number.isNaN(Number(value))) throw Error(lang?.number);
1360
- break;
1361
- }
1362
- },
1363
- custom: async (value, options) => {
1364
- return options(value);
1365
- }
1366
- };
1367
- async function validValues(rules, items, values, lang) {
1368
- const errors = {};
1369
- for (const item of items) {
1370
- const value = values[item.name];
1371
- const rulesOptions = item.rules;
1372
- if (rulesOptions) for (const [name, options] of Object.entries(rulesOptions)) try {
1373
- await rules[name](value, options, lang);
1374
- } catch (error) {
1375
- errors[item.name] = error;
1376
- break;
1377
- }
1378
- }
1379
- return errors;
1380
- }
1381
- //#endregion
1382
- //#region src/Form/Footer.tsx
1383
- function FormFooter() {
1384
- const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
1385
- const Button = Elements.Button;
1386
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1387
- submitting,
1388
- submit: (0, react.useCallback)(async () => {
1389
- setSubmitting(true);
1390
- setErrors({});
1391
- const errors = await validValues(rules, items, valuesRef.current, lang);
1392
- if (Object.keys(errors).length) {
1393
- setErrors(errors);
1394
- setSubmitting(false);
1395
- return;
1396
- }
1397
- onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1398
- }, [
1399
- setSubmitting,
1400
- setErrors,
1401
- rules,
1402
- items,
1403
- valuesRef,
1404
- lang,
1405
- onSubmit
1406
- ]),
1407
- children: lang.submit
1408
- });
1409
- }
1410
- FormFooter.displayName = "FormFooter";
1411
- //#endregion
1412
- //#region src/Form/lang.ts
1413
- const FormDefaultLang = {
1414
- submit: "Submit",
1415
- required: "This field is required",
1416
- string: "This field must be a string",
1417
- number: "This field must be a number"
1418
- };
1419
- //#endregion
1420
- //#region src/Form/Container.tsx
1421
- function mergeValues(items, defaultValues = {}) {
1422
- const values = {};
1423
- for (const item of items) values[item.name] = defaultValues[item.name] ?? "";
1424
- return values;
1425
- }
1426
- /**
1427
- * Render a form with context, default elements, and validation state.
1428
- *
1429
- * `FormContainer` merges provided elements, language strings, and rules with
1430
- * the package defaults, then exposes them through form context.
1431
- *
1432
- * @template Values - The type of form values, defaults to Record<string, any>.
1433
- * @template FormElements - The type of form elements, defaults to FormElementTypes.
1434
- * @template Rules - The type of form rules, defaults to FormDefaultRules.
1435
- * @param props - Form items and optional overrides for defaults, language, rules, and submit behavior.
1436
- * @returns React form container with shared form context.
1437
- *
1438
- * @example
1439
- * ```tsx
1440
- * import { Form } from '@faasjs/react'
1441
- *
1442
- * function MyForm() {
1443
- * return (
1444
- * <Form
1445
- * items={[
1446
- * { name: 'name' },
1447
- * ]}
1448
- * />
1449
- * )
1450
- * }
1451
- * ```
1452
- */
1453
- function FormContainer({ defaultValues, Elements, rules, lang, items, ...props }) {
1454
- const [values, setValues, valuesRef] = useStateRef(mergeValues(items, defaultValues));
1455
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FormContextProvider, {
1456
- initializeStates: {
1457
- errors: {},
1458
- submitting: false
1459
- },
1460
- value: {
1461
- Elements: Object.assign(FormDefaultElements, Elements),
1462
- lang: Object.assign(FormDefaultLang, lang),
1463
- rules: Object.assign(FormDefaultRules, rules),
1464
- items,
1465
- values,
1466
- setValues,
1467
- valuesRef,
1468
- ...props
1469
- },
1470
- memo: true,
1471
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormBody, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFooter, {})]
1472
- });
1473
- }
1474
- FormContainer.displayName = "FormContainer";
1475
- //#endregion
1476
- //#region src/OptionalWrapper.tsx
1477
- /**
1478
- * A wrapper component that conditionally wraps its children with a provided wrapper component.
1479
- *
1480
- * @example
1481
- * ```tsx
1482
- * import { OptionalWrapper } from '@faasjs/react'
1483
- *
1484
- * const Wrapper = ({ children }: { children: React.ReactNode }) => (
1485
- * <div className='wrapper'>{children}</div>
1486
- * )
1487
- *
1488
- * const App = () => (
1489
- * <OptionalWrapper condition={true} Wrapper={Wrapper}>
1490
- * <span>Test</span>
1491
- * </OptionalWrapper>
1492
- * )
1493
- * ```
1494
- */
1495
- function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
1496
- if (condition) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, {
1497
- ...wrapperProps,
1498
- children
1499
- });
1500
- return children;
1501
- }
1502
- OptionalWrapper.displayName = "OptionalWrapper";
1503
- //#endregion
1504
1222
  //#region src/useFaasStream.tsx
1505
1223
  /**
1506
1224
  * Stream a FaasJS response into React state.
@@ -1669,17 +1387,48 @@ function usePrevious(value) {
1669
1387
  return ref.current;
1670
1388
  }
1671
1389
  //#endregion
1390
+ //#region src/useStateRef.ts
1391
+ /**
1392
+ * Custom hook that returns a stateful value and a ref to that value.
1393
+ *
1394
+ * @template T - The type of the value.
1395
+ * @param initialValue - Initial state value. When omitted, state starts as `null`.
1396
+ * @returns Tuple containing the current state, the state setter, and a ref that always points at the latest state.
1397
+ *
1398
+ * @example
1399
+ * ```tsx
1400
+ * import { useStateRef } from '@faasjs/react'
1401
+ *
1402
+ * function MyComponent() {
1403
+ * const [value, setValue, ref] = useStateRef(0)
1404
+ *
1405
+ * return (
1406
+ * <div>
1407
+ * <p>Value: {value}</p>
1408
+ * <button onClick={() => setValue(value + 1)}>Increment</button>
1409
+ * <button onClick={() => console.log(ref.current)}>Submit</button>
1410
+ * </div>
1411
+ * )
1412
+ * }
1413
+ * ```
1414
+ */
1415
+ function useStateRef(initialValue) {
1416
+ const [state, setState] = (0, react.useState)(initialValue ?? null);
1417
+ const ref = (0, react.useRef)(state);
1418
+ (0, react.useEffect)(() => {
1419
+ ref.current = state;
1420
+ }, [state]);
1421
+ return [
1422
+ state,
1423
+ setState,
1424
+ ref
1425
+ ];
1426
+ }
1427
+ //#endregion
1672
1428
  exports.ErrorBoundary = ErrorBoundary;
1673
1429
  exports.FaasBrowserClient = FaasBrowserClient;
1674
1430
  exports.FaasDataWrapper = FaasDataWrapper;
1675
1431
  exports.FaasReactClient = FaasReactClient;
1676
- exports.Form = FormContainer;
1677
- exports.FormContextProvider = FormContextProvider;
1678
- exports.FormDefaultElements = FormDefaultElements;
1679
- exports.FormDefaultLang = FormDefaultLang;
1680
- exports.FormDefaultRules = FormDefaultRules;
1681
- exports.FormInput = FormInput;
1682
- exports.FormItem = FormItem;
1683
1432
  exports.OptionalWrapper = OptionalWrapper;
1684
1433
  exports.Response = Response;
1685
1434
  exports.ResponseError = ResponseError;
@@ -1696,9 +1445,7 @@ exports.useEqualMemo = useEqualMemo;
1696
1445
  exports.useEqualMemoize = useEqualMemoize;
1697
1446
  exports.useFaas = useFaas;
1698
1447
  exports.useFaasStream = useFaasStream;
1699
- exports.useFormContext = useFormContext;
1700
1448
  exports.usePrevious = usePrevious;
1701
1449
  exports.useSplittingState = useSplittingState;
1702
1450
  exports.useStateRef = useStateRef;
1703
- exports.validValues = validValues;
1704
1451
  exports.withFaasData = withFaasData;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FaasAction, FaasAction as FaasAction$1, FaasActionUnionType, FaasActionUnionType as FaasActionUnionType$1, FaasData, FaasData as FaasData$1, FaasParams, FaasParams as FaasParams$1 } from "@faasjs/types";
2
2
  import * as react from "react";
3
- import { Component, ComponentProps, ComponentType, Dispatch, ErrorInfo, JSX, JSXElementConstructor, ReactElement, ReactNode, RefObject, SetStateAction } from "react";
3
+ import { Component, ComponentProps, ComponentType, Dispatch, ErrorInfo, JSX, ReactElement, ReactNode, RefObject, SetStateAction } from "react";
4
4
  import * as react_jsx_runtime0 from "react/jsx-runtime";
5
5
 
6
6
  //#region src/generateId.d.ts
@@ -1041,220 +1041,6 @@ declare function useEqualMemo<T>(callback: () => T, dependencies: any[]): T;
1041
1041
  */
1042
1042
  declare function useEqualCallback<T extends (...args: any[]) => any>(callback: T, dependencies: any[]): T;
1043
1043
  //#endregion
1044
- //#region src/Form/elements/Button.d.ts
1045
- /**
1046
- * Props for the FormButtonElement component.
1047
- *
1048
- * @property {React.ReactNode} [children] - The content to be displayed inside the button.
1049
- * @property {boolean} disabled - Indicates whether the button is disabled.
1050
- * @property {() => Promise<void>} submit - A function to be called when the button is clicked, which returns a promise.
1051
- */
1052
- type FormButtonElementProps = {
1053
- children?: React.ReactNode;
1054
- submitting: boolean;
1055
- submit: () => Promise<void>;
1056
- };
1057
- //#endregion
1058
- //#region src/Form/elements/Input.d.ts
1059
- /**
1060
- * Props for the Form Input Element component.
1061
- *
1062
- * @property {string} name - The name of the input element.
1063
- * @property {any} value - The current value of the input element.
1064
- * @property {(value: any) => void} onChange - Callback function to handle changes to the input value.
1065
- */
1066
- type FormInputElementProps = {
1067
- name: string;
1068
- value: any;
1069
- onChange: (value: any) => void;
1070
- };
1071
- //#endregion
1072
- //#region src/Form/elements/Label.d.ts
1073
- /**
1074
- * Props for the FormLabelElement component.
1075
- *
1076
- * @typedef {Object} FormLabelElementProps
1077
- * @property {string} name - The name of the form element.
1078
- * @property {ReactNode} [title] - Optional title for the form element.
1079
- * @property {ReactNode} [description] - Optional description for the form element.
1080
- * @property {Error} [error] - Optional error associated with the form element.
1081
- * @property {ReactNode} children - The child elements, typically an input element.
1082
- */
1083
- type FormLabelElementProps = {
1084
- name: string;
1085
- title?: ReactNode;
1086
- description?: ReactNode;
1087
- error?: Error; /** as Input element */
1088
- children: ReactNode;
1089
- };
1090
- //#endregion
1091
- //#region src/Form/elements/index.d.ts
1092
- /**
1093
- * Represents the types of form elements used in the form.
1094
- *
1095
- * @typedef {Object} FormElementTypes
1096
- * @property {ComponentType<FormLabelElementProps>} Label - The component type for the form label element.
1097
- * @property {ComponentType<FormInputElementProps>} Input - The component type for the form input element.
1098
- * @property {ComponentType<FormButtonElementProps>} Button - The component type for the form button element.
1099
- */
1100
- type FormElementTypes = {
1101
- Label: ComponentType<FormLabelElementProps>;
1102
- Input: ComponentType<FormInputElementProps>;
1103
- Button: ComponentType<FormButtonElementProps>;
1104
- };
1105
- declare const FormDefaultElements: FormElementTypes;
1106
- //#endregion
1107
- //#region src/Form/lang.d.ts
1108
- declare const FormDefaultLang: {
1109
- submit: string;
1110
- required: string;
1111
- string: string;
1112
- number: string;
1113
- };
1114
- type FormLang = typeof FormDefaultLang;
1115
- //#endregion
1116
- //#region src/Form/rules.d.ts
1117
- /**
1118
- * A type representing a form validation rule.
1119
- *
1120
- * @template Options - The type of the options that can be passed to the rule.
1121
- *
1122
- * @param value - The value to be validated.
1123
- * @param options - Optional. Additional options that can be used in the validation.
1124
- * @param lang - Optional. The language settings that can be used in the validation.
1125
- *
1126
- * @returns A promise that resolves if the validation is successful, or rejects with an error if the validation fails.
1127
- *
1128
- * @example
1129
- * ```ts
1130
- * async function required(value: any, options: boolean, lang?: FormLang) {
1131
- * if (value === null || value === undefined || value === '' || Number.isNaN(value))
1132
- * throw Error(lang?.required)
1133
- * }
1134
- * ```
1135
- */
1136
- type FormRule<Options = any> = (value: any, options?: Options, lang?: FormLang) => Promise<void>;
1137
- type InferRuleOption<T> = T extends ((value: any, options: infer O, lang?: FormLang) => Promise<void>) ? O : never;
1138
- /**
1139
- * A type representing a set of form validation rules.
1140
- *
1141
- * @typedef {Record<string, FormRule>} FormRules
1142
- *
1143
- * Each key in the record represents the name of a form field, and the corresponding value is a `FormRule` object that defines the validation rules for that field.
1144
- */
1145
- type FormRules = Record<string, FormRule>;
1146
- type InferFormRulesOptions<T> = { [K in keyof T]: InferRuleOption<T[K]> };
1147
- /**
1148
- * Default validation rules for a form.
1149
- */
1150
- declare const FormDefaultRules: FormRules;
1151
- type FormDefaultRulesOptions = InferFormRulesOptions<typeof FormDefaultRules>;
1152
- declare function validValues(rules: FormRules, items: FormItemProps[], values: Record<string, any>, lang: FormLang): Promise<Record<string, Error>>;
1153
- //#endregion
1154
- //#region src/Form/Input.d.ts
1155
- type InferFormInputProps<T extends ComponentType<FormInputElementProps> | JSXElementConstructor<any>> = T extends ComponentType<FormInputElementProps> ? Omit<ComponentProps<T>, 'name' | 'value' | 'onChange'> : Omit<ComponentProps<T>, 'name' | 'value'>;
1156
- type FormInputProps<FormElements extends FormElementTypes = FormElementTypes> = {
1157
- Input?: ComponentType<FormInputElementProps>;
1158
- props?: InferFormInputProps<FormElements['Input']>;
1159
- };
1160
- declare function FormInput({
1161
- name,
1162
- rules,
1163
- ...rest
1164
- }: FormInputProps & {
1165
- name: string;
1166
- rules?: FormDefaultRulesOptions;
1167
- }): react_jsx_runtime0.JSX.Element;
1168
- declare namespace FormInput {
1169
- var displayName: string;
1170
- }
1171
- //#endregion
1172
- //#region src/Form/Item.d.ts
1173
- type FormItemName = string;
1174
- type FormItemProps<FormElements extends FormElementTypes = FormElementTypes, FormRulesOptions extends Record<string, any> = FormDefaultRulesOptions> = {
1175
- name: FormItemName;
1176
- label?: Omit<FormLabelElementProps, 'name' | 'children'> & {
1177
- Label?: ComponentType<FormLabelElementProps>;
1178
- };
1179
- input?: FormInputProps<FormElements>;
1180
- rules?: FormRulesOptions;
1181
- };
1182
- declare function FormItem(props: FormItemProps): react_jsx_runtime0.JSX.Element;
1183
- declare namespace FormItem {
1184
- var displayName: string;
1185
- }
1186
- //#endregion
1187
- //#region src/Form/Container.d.ts
1188
- type FormProps<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes, Rules extends FormRules = typeof FormDefaultRules> = {
1189
- items: FormItemProps<FormElements, InferFormRulesOptions<Rules>>[];
1190
- onSubmit?: (values: Values) => Promise<void>;
1191
- Elements?: Partial<FormElements>;
1192
- lang?: Partial<FormLang>;
1193
- defaultValues?: Values;
1194
- rules?: typeof FormDefaultRules & Rules;
1195
- };
1196
- /**
1197
- * Render a form with context, default elements, and validation state.
1198
- *
1199
- * `FormContainer` merges provided elements, language strings, and rules with
1200
- * the package defaults, then exposes them through form context.
1201
- *
1202
- * @template Values - The type of form values, defaults to Record<string, any>.
1203
- * @template FormElements - The type of form elements, defaults to FormElementTypes.
1204
- * @template Rules - The type of form rules, defaults to FormDefaultRules.
1205
- * @param props - Form items and optional overrides for defaults, language, rules, and submit behavior.
1206
- * @returns React form container with shared form context.
1207
- *
1208
- * @example
1209
- * ```tsx
1210
- * import { Form } from '@faasjs/react'
1211
- *
1212
- * function MyForm() {
1213
- * return (
1214
- * <Form
1215
- * items={[
1216
- * { name: 'name' },
1217
- * ]}
1218
- * />
1219
- * )
1220
- * }
1221
- * ```
1222
- */
1223
- declare function FormContainer<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes, Rules extends FormRules = typeof FormDefaultRules>({
1224
- defaultValues,
1225
- Elements,
1226
- rules,
1227
- lang,
1228
- items,
1229
- ...props
1230
- }: FormProps<Values, FormElements, Rules>): react_jsx_runtime0.JSX.Element;
1231
- declare namespace FormContainer {
1232
- var displayName: string;
1233
- }
1234
- //#endregion
1235
- //#region src/Form/context.d.ts
1236
- type FormContextProps<Values extends Record<string, any> = Record<string, any>, FormElements extends FormElementTypes = FormElementTypes, Rules extends FormRules = typeof FormDefaultRules> = {
1237
- items: FormItemProps<FormElements, InferFormRulesOptions<Rules>>[];
1238
- onSubmit: (values: Values) => Promise<void>;
1239
- Elements: FormElementTypes;
1240
- lang: FormLang;
1241
- rules: typeof FormDefaultRules & Rules;
1242
- submitting: boolean;
1243
- setSubmitting: Dispatch<SetStateAction<boolean>>;
1244
- values: Values;
1245
- setValues: Dispatch<SetStateAction<Values>>;
1246
- errors: Record<string, Error>;
1247
- setErrors: Dispatch<SetStateAction<Record<string, Error>>>;
1248
- valuesRef: RefObject<Values>;
1249
- };
1250
- declare const FormContextProvider: <NewT extends FormContextProps<Record<string, any>, FormElementTypes, FormRules> = FormContextProps<Record<string, any>, FormElementTypes, FormRules>>(this: void, props: {
1251
- value?: Partial<NewT>;
1252
- children: react.ReactNode;
1253
- memo?: true | any[];
1254
- initializeStates?: Partial<NewT>;
1255
- }) => react.ReactNode;
1256
- declare const useFormContext: <NewT extends FormContextProps<Record<string, any>, FormElementTypes, FormRules> = FormContextProps<Record<string, any>, FormElementTypes, FormRules>>(this: void) => Readonly<NewT>;
1257
- //#endregion
1258
1044
  //#region src/OptionalWrapper.d.ts
1259
1045
  type OptionalWrapperProps<TWrapper extends ComponentType<{
1260
1046
  children: ReactNode;
@@ -1522,4 +1308,4 @@ declare function usePrevious<T = any>(value: T): T | undefined;
1522
1308
  */
1523
1309
  declare function useStateRef<T = any>(initialValue?: T): [T | null, Dispatch<SetStateAction<T | null>>, RefObject<T | null>];
1524
1310
  //#endregion
1525
- export { BaseUrl, ErrorBoundary, ErrorBoundaryProps, ErrorChildrenProps, type FaasAction, type FaasActionUnionType, FaasBrowserClient, FaasBrowserClientAction, type FaasData, FaasDataInjection, FaasDataWrapper, FaasDataWrapperProps, FaasDataWrapperRef, type FaasParams, FaasReactClient, FaasReactClientInstance, FaasReactClientOptions, FormContainer as Form, type FormButtonElementProps, FormContextProps, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormDefaultRulesOptions, FormElementTypes, FormInput, type FormInputElementProps, FormInputProps, FormItem, FormItemName, FormItemProps, type FormLabelElementProps, FormLang, FormProps, FormRule, FormRules, InferFormInputProps, InferFormRulesOptions, InferRuleOption, MockHandler, OnError, OptionalWrapper, OptionalWrapperProps, Options, Response, ResponseError, ResponseErrorProps, ResponseHeaders, ResponseProps, StateSetters, StatesWithSetters, UseFaasStreamOptions, UseFaasStreamResult, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasOptions, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };
1311
+ export { BaseUrl, ErrorBoundary, ErrorBoundaryProps, ErrorChildrenProps, type FaasAction, type FaasActionUnionType, FaasBrowserClient, FaasBrowserClientAction, type FaasData, FaasDataInjection, FaasDataWrapper, FaasDataWrapperProps, FaasDataWrapperRef, type FaasParams, FaasReactClient, FaasReactClientInstance, FaasReactClientOptions, MockHandler, OnError, OptionalWrapper, OptionalWrapperProps, Options, Response, ResponseError, ResponseErrorProps, ResponseHeaders, ResponseProps, StateSetters, StatesWithSetters, UseFaasStreamOptions, UseFaasStreamResult, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasOptions, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
package/dist/index.mjs CHANGED
@@ -1076,43 +1076,33 @@ var ErrorBoundary = class extends Component {
1076
1076
  }
1077
1077
  };
1078
1078
  //#endregion
1079
- //#region src/useStateRef.ts
1079
+ //#region src/OptionalWrapper.tsx
1080
1080
  /**
1081
- * Custom hook that returns a stateful value and a ref to that value.
1082
- *
1083
- * @template T - The type of the value.
1084
- * @param initialValue - Initial state value. When omitted, state starts as `null`.
1085
- * @returns Tuple containing the current state, the state setter, and a ref that always points at the latest state.
1081
+ * A wrapper component that conditionally wraps its children with a provided wrapper component.
1086
1082
  *
1087
1083
  * @example
1088
1084
  * ```tsx
1089
- * import { useStateRef } from '@faasjs/react'
1085
+ * import { OptionalWrapper } from '@faasjs/react'
1090
1086
  *
1091
- * function MyComponent() {
1092
- * const [value, setValue, ref] = useStateRef(0)
1087
+ * const Wrapper = ({ children }: { children: React.ReactNode }) => (
1088
+ * <div className='wrapper'>{children}</div>
1089
+ * )
1093
1090
  *
1094
- * return (
1095
- * <div>
1096
- * <p>Value: {value}</p>
1097
- * <button onClick={() => setValue(value + 1)}>Increment</button>
1098
- * <button onClick={() => console.log(ref.current)}>Submit</button>
1099
- * </div>
1100
- * )
1101
- * }
1091
+ * const App = () => (
1092
+ * <OptionalWrapper condition={true} Wrapper={Wrapper}>
1093
+ * <span>Test</span>
1094
+ * </OptionalWrapper>
1095
+ * )
1102
1096
  * ```
1103
1097
  */
1104
- function useStateRef(initialValue) {
1105
- const [state, setState] = useState(initialValue ?? null);
1106
- const ref = useRef(state);
1107
- useEffect(() => {
1108
- ref.current = state;
1109
- }, [state]);
1110
- return [
1111
- state,
1112
- setState,
1113
- ref
1114
- ];
1098
+ function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
1099
+ if (condition) return /* @__PURE__ */ jsx(Wrapper, {
1100
+ ...wrapperProps,
1101
+ children
1102
+ });
1103
+ return children;
1115
1104
  }
1105
+ OptionalWrapper.displayName = "OptionalWrapper";
1116
1106
  //#endregion
1117
1107
  //#region src/splittingState.tsx
1118
1108
  /**
@@ -1225,278 +1215,6 @@ function createSplittingContext(defaultValue) {
1225
1215
  };
1226
1216
  }
1227
1217
  //#endregion
1228
- //#region src/Form/context.tsx
1229
- const FormContext = createSplittingContext([
1230
- "items",
1231
- "onSubmit",
1232
- "Elements",
1233
- "lang",
1234
- "rules",
1235
- "submitting",
1236
- "setSubmitting",
1237
- "values",
1238
- "setValues",
1239
- "errors",
1240
- "setErrors",
1241
- "valuesRef"
1242
- ]);
1243
- const FormContextProvider = FormContext.Provider;
1244
- const useFormContext = FormContext.use;
1245
- //#endregion
1246
- //#region src/Form/Input.tsx
1247
- function processValue(input, rules) {
1248
- switch (rules?.type) {
1249
- case "number": return Number(input);
1250
- case "string": return String(input);
1251
- default: return input;
1252
- }
1253
- }
1254
- function FormInput({ name, rules, ...rest }) {
1255
- const { Elements, values, setValues } = useFormContext();
1256
- const value = values?.[name];
1257
- if (rest.Input) return /* @__PURE__ */ jsx(rest.Input, {
1258
- name,
1259
- value,
1260
- onChange: (v) => setValues((prev) => ({
1261
- ...prev,
1262
- [name]: processValue(v, rules)
1263
- })),
1264
- ...rest.props
1265
- });
1266
- return /* @__PURE__ */ jsx(Elements.Input, {
1267
- name,
1268
- value,
1269
- onChange: (v) => setValues((prev) => ({
1270
- ...prev,
1271
- [name]: processValue(v, rules)
1272
- })),
1273
- ...rest.props
1274
- });
1275
- }
1276
- FormInput.displayName = "FormInput";
1277
- //#endregion
1278
- //#region src/Form/Item.tsx
1279
- function FormItem(props) {
1280
- const { Elements, errors } = useFormContext();
1281
- return /* @__PURE__ */ jsx(props.label?.Label ?? Elements.Label, {
1282
- name: props.name,
1283
- ...props.label,
1284
- error: errors[props.name],
1285
- children: /* @__PURE__ */ jsx(FormInput, {
1286
- name: props.name,
1287
- ...props.input,
1288
- ...props.rules ? { rules: props.rules } : {}
1289
- })
1290
- });
1291
- }
1292
- FormItem.displayName = "FormItem";
1293
- //#endregion
1294
- //#region src/Form/Body.tsx
1295
- function FormBody() {
1296
- const { items } = useFormContext();
1297
- return items.map((item) => /* @__PURE__ */ jsx(FormItem, { ...item }, item.name));
1298
- }
1299
- FormBody.displayName = "FormBody";
1300
- //#endregion
1301
- //#region src/Form/elements/Button.tsx
1302
- const FormButtonElement = forwardRef(({ children, submit, submitting, ...props }, ref) => /* @__PURE__ */ jsx("button", {
1303
- type: "button",
1304
- disabled: submitting,
1305
- onClick: submit,
1306
- ...props,
1307
- ref,
1308
- children
1309
- }));
1310
- FormButtonElement.displayName = "FormButtonElement";
1311
- //#endregion
1312
- //#region src/Form/elements/Input.tsx
1313
- const FormInputElement = forwardRef(({ onChange, ...props }, ref) => /* @__PURE__ */ jsx("input", {
1314
- ...props,
1315
- onChange: (e) => onChange(e.target.value),
1316
- ref
1317
- }));
1318
- FormInputElement.displayName = "FormInputElement";
1319
- //#endregion
1320
- //#region src/Form/elements/Label.tsx
1321
- const FormLabelElement = ({ name, title, description, error, children }) => {
1322
- return /* @__PURE__ */ jsxs("label", { children: [
1323
- title ?? name,
1324
- children,
1325
- description,
1326
- error && /* @__PURE__ */ jsx("div", {
1327
- style: { color: "red" },
1328
- children: error.message
1329
- })
1330
- ] });
1331
- };
1332
- FormLabelElement.displayName = "FormLabelElement";
1333
- //#endregion
1334
- //#region src/Form/elements/index.ts
1335
- const FormDefaultElements = {
1336
- Label: FormLabelElement,
1337
- Input: FormInputElement,
1338
- Button: FormButtonElement
1339
- };
1340
- //#endregion
1341
- //#region src/Form/rules.ts
1342
- /**
1343
- * Default validation rules for a form.
1344
- */
1345
- const FormDefaultRules = {
1346
- required: async (value, _, lang) => {
1347
- if (value === null || value === void 0 || value === "" || Number.isNaN(value)) throw Error(lang?.required);
1348
- },
1349
- type: async (value, options, lang) => {
1350
- switch (options) {
1351
- case "string":
1352
- if (typeof value !== "string") throw Error(lang?.string);
1353
- break;
1354
- case "number":
1355
- if (Number.isNaN(Number(value))) throw Error(lang?.number);
1356
- break;
1357
- }
1358
- },
1359
- custom: async (value, options) => {
1360
- return options(value);
1361
- }
1362
- };
1363
- async function validValues(rules, items, values, lang) {
1364
- const errors = {};
1365
- for (const item of items) {
1366
- const value = values[item.name];
1367
- const rulesOptions = item.rules;
1368
- if (rulesOptions) for (const [name, options] of Object.entries(rulesOptions)) try {
1369
- await rules[name](value, options, lang);
1370
- } catch (error) {
1371
- errors[item.name] = error;
1372
- break;
1373
- }
1374
- }
1375
- return errors;
1376
- }
1377
- //#endregion
1378
- //#region src/Form/Footer.tsx
1379
- function FormFooter() {
1380
- const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
1381
- const Button = Elements.Button;
1382
- return /* @__PURE__ */ jsx(Button, {
1383
- submitting,
1384
- submit: useCallback(async () => {
1385
- setSubmitting(true);
1386
- setErrors({});
1387
- const errors = await validValues(rules, items, valuesRef.current, lang);
1388
- if (Object.keys(errors).length) {
1389
- setErrors(errors);
1390
- setSubmitting(false);
1391
- return;
1392
- }
1393
- onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1394
- }, [
1395
- setSubmitting,
1396
- setErrors,
1397
- rules,
1398
- items,
1399
- valuesRef,
1400
- lang,
1401
- onSubmit
1402
- ]),
1403
- children: lang.submit
1404
- });
1405
- }
1406
- FormFooter.displayName = "FormFooter";
1407
- //#endregion
1408
- //#region src/Form/lang.ts
1409
- const FormDefaultLang = {
1410
- submit: "Submit",
1411
- required: "This field is required",
1412
- string: "This field must be a string",
1413
- number: "This field must be a number"
1414
- };
1415
- //#endregion
1416
- //#region src/Form/Container.tsx
1417
- function mergeValues(items, defaultValues = {}) {
1418
- const values = {};
1419
- for (const item of items) values[item.name] = defaultValues[item.name] ?? "";
1420
- return values;
1421
- }
1422
- /**
1423
- * Render a form with context, default elements, and validation state.
1424
- *
1425
- * `FormContainer` merges provided elements, language strings, and rules with
1426
- * the package defaults, then exposes them through form context.
1427
- *
1428
- * @template Values - The type of form values, defaults to Record<string, any>.
1429
- * @template FormElements - The type of form elements, defaults to FormElementTypes.
1430
- * @template Rules - The type of form rules, defaults to FormDefaultRules.
1431
- * @param props - Form items and optional overrides for defaults, language, rules, and submit behavior.
1432
- * @returns React form container with shared form context.
1433
- *
1434
- * @example
1435
- * ```tsx
1436
- * import { Form } from '@faasjs/react'
1437
- *
1438
- * function MyForm() {
1439
- * return (
1440
- * <Form
1441
- * items={[
1442
- * { name: 'name' },
1443
- * ]}
1444
- * />
1445
- * )
1446
- * }
1447
- * ```
1448
- */
1449
- function FormContainer({ defaultValues, Elements, rules, lang, items, ...props }) {
1450
- const [values, setValues, valuesRef] = useStateRef(mergeValues(items, defaultValues));
1451
- return /* @__PURE__ */ jsxs(FormContextProvider, {
1452
- initializeStates: {
1453
- errors: {},
1454
- submitting: false
1455
- },
1456
- value: {
1457
- Elements: Object.assign(FormDefaultElements, Elements),
1458
- lang: Object.assign(FormDefaultLang, lang),
1459
- rules: Object.assign(FormDefaultRules, rules),
1460
- items,
1461
- values,
1462
- setValues,
1463
- valuesRef,
1464
- ...props
1465
- },
1466
- memo: true,
1467
- children: [/* @__PURE__ */ jsx(FormBody, {}), /* @__PURE__ */ jsx(FormFooter, {})]
1468
- });
1469
- }
1470
- FormContainer.displayName = "FormContainer";
1471
- //#endregion
1472
- //#region src/OptionalWrapper.tsx
1473
- /**
1474
- * A wrapper component that conditionally wraps its children with a provided wrapper component.
1475
- *
1476
- * @example
1477
- * ```tsx
1478
- * import { OptionalWrapper } from '@faasjs/react'
1479
- *
1480
- * const Wrapper = ({ children }: { children: React.ReactNode }) => (
1481
- * <div className='wrapper'>{children}</div>
1482
- * )
1483
- *
1484
- * const App = () => (
1485
- * <OptionalWrapper condition={true} Wrapper={Wrapper}>
1486
- * <span>Test</span>
1487
- * </OptionalWrapper>
1488
- * )
1489
- * ```
1490
- */
1491
- function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
1492
- if (condition) return /* @__PURE__ */ jsx(Wrapper, {
1493
- ...wrapperProps,
1494
- children
1495
- });
1496
- return children;
1497
- }
1498
- OptionalWrapper.displayName = "OptionalWrapper";
1499
- //#endregion
1500
1218
  //#region src/useFaasStream.tsx
1501
1219
  /**
1502
1220
  * Stream a FaasJS response into React state.
@@ -1665,4 +1383,42 @@ function usePrevious(value) {
1665
1383
  return ref.current;
1666
1384
  }
1667
1385
  //#endregion
1668
- export { ErrorBoundary, FaasBrowserClient, FaasDataWrapper, FaasReactClient, FormContainer as Form, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormInput, FormItem, OptionalWrapper, Response, ResponseError, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };
1386
+ //#region src/useStateRef.ts
1387
+ /**
1388
+ * Custom hook that returns a stateful value and a ref to that value.
1389
+ *
1390
+ * @template T - The type of the value.
1391
+ * @param initialValue - Initial state value. When omitted, state starts as `null`.
1392
+ * @returns Tuple containing the current state, the state setter, and a ref that always points at the latest state.
1393
+ *
1394
+ * @example
1395
+ * ```tsx
1396
+ * import { useStateRef } from '@faasjs/react'
1397
+ *
1398
+ * function MyComponent() {
1399
+ * const [value, setValue, ref] = useStateRef(0)
1400
+ *
1401
+ * return (
1402
+ * <div>
1403
+ * <p>Value: {value}</p>
1404
+ * <button onClick={() => setValue(value + 1)}>Increment</button>
1405
+ * <button onClick={() => console.log(ref.current)}>Submit</button>
1406
+ * </div>
1407
+ * )
1408
+ * }
1409
+ * ```
1410
+ */
1411
+ function useStateRef(initialValue) {
1412
+ const [state, setState] = useState(initialValue ?? null);
1413
+ const ref = useRef(state);
1414
+ useEffect(() => {
1415
+ ref.current = state;
1416
+ }, [state]);
1417
+ return [
1418
+ state,
1419
+ setState,
1420
+ ref
1421
+ ];
1422
+ }
1423
+ //#endregion
1424
+ export { ErrorBoundary, FaasBrowserClient, FaasDataWrapper, FaasReactClient, OptionalWrapper, Response, ResponseError, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "8.0.0-beta.16",
3
+ "version": "8.0.0-beta.17",
4
4
  "homepage": "https://faasjs.com/doc/react/",
5
5
  "bugs": {
6
6
  "url": "https://github.com/faasjs/faasjs/issues"
@@ -27,12 +27,12 @@
27
27
  }
28
28
  },
29
29
  "devDependencies": {
30
- "@faasjs/types": ">=8.0.0-beta.16",
30
+ "@faasjs/types": ">=8.0.0-beta.17",
31
31
  "@types/react": "^19.0.0",
32
32
  "react": "^19.0.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@faasjs/types": ">=8.0.0-beta.16"
35
+ "@faasjs/types": ">=8.0.0-beta.17"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=24.0.0",