@arcote.tech/arc-react 0.1.8 → 0.1.10

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.
@@ -9,16 +9,18 @@ export declare function useFormField(): FormFieldContext;
9
9
  type Translations<E extends ArcElement> = {
10
10
  [K in keyof Exclude<ReturnType<E["validate"]>, false>]: (data: Exclude<Exclude<ReturnType<E["validate"]>, false>[K], undefined | false>) => string;
11
11
  } | ((data: any) => string) | string;
12
- type FormFieldProps<E extends ArcElement> = {
12
+ type FormFieldProps<E extends ArcElement, S = undefined> = {
13
13
  translations: Translations<E>;
14
- render: (field: FormFieldData<E>) => React.ReactNode;
14
+ render: (field: FormFieldData<E, S>) => React.ReactNode;
15
15
  };
16
- export type FormFieldData<T> = {
16
+ export type FormFieldData<T, S = undefined> = {
17
17
  onChange: (value: any) => void;
18
18
  value: any;
19
19
  defaultValue?: any;
20
20
  name: string;
21
+ subFields?: S;
22
+ setFieldValue: (field: string, value: any) => void;
21
23
  };
22
- export declare function FormField<E extends ArcElement>(name: string, defaultValue?: any): ({ translations, render }: FormFieldProps<E>) => import("react/jsx-dev-runtime").JSX.Element;
24
+ export declare function FormField<E extends ArcElement, S = undefined>(name: string, defaultValue?: any, subFields?: S): ({ translations, render }: FormFieldProps<E, S>) => import("react/jsx-dev-runtime").JSX.Element;
23
25
  export {};
24
26
  //# sourceMappingURL=field.d.ts.map
@@ -1,4 +1,4 @@
1
- import { type $type, type ArcObjectAny, type ArcObjectKeys, type ArcObjectValueByKey } from "@arcote.tech/arc";
1
+ import { type $type, type ArcObjectAny, type ArcObjectKeys, type ArcObjectValueByKey, ArcOptional } from "@arcote.tech/arc";
2
2
  import React from "react";
3
3
  import { FormField } from "./field";
4
4
  export type FormContextValue<T extends ArcObjectAny> = {
@@ -14,16 +14,19 @@ export type FormRef<T extends ArcObjectAny> = {
14
14
  submit: () => Promise<void>;
15
15
  getValues: () => Partial<$type<T>>;
16
16
  validate: () => boolean;
17
+ setFieldValue: (field: ArcObjectKeys<T>, value: any) => void;
18
+ };
19
+ export type FormFields<T extends ArcObjectAny> = {
20
+ [K in ArcObjectKeys<T> as Capitalize<`${K}`>]: ArcObjectValueByKey<T, K> extends ArcObjectAny ? ReturnType<typeof FormField<ArcObjectValueByKey<T, K>, FormFields<ArcObjectValueByKey<T, K>>>> : ArcObjectValueByKey<T, K> extends ArcOptional<infer U extends ArcObjectAny> ? ReturnType<typeof FormField<ArcObjectValueByKey<T, K>, FormFields<U>>> : ReturnType<typeof FormField<ArcObjectValueByKey<T, K>>>;
17
21
  };
18
22
  export type FormProps<T extends ArcObjectAny> = {
19
- render: (props: {
20
- [K in ArcObjectKeys<T> as Capitalize<`${K}`>]: ReturnType<typeof FormField<ArcObjectValueByKey<T, K>>>;
21
- }, values: Partial<$type<T>>) => React.ReactNode;
23
+ render: (props: FormFields<T>, values: Partial<$type<T>>) => React.ReactNode;
22
24
  schema: T;
23
25
  onSubmit: (values: $type<T>) => void | Promise<void>;
24
26
  onUnvalidatedSubmit?: (values: Partial<$type<T>>, errors: any) => void;
25
27
  defaults?: Partial<$type<T>> | null;
26
28
  };
27
29
  export declare const FormContext: React.Context<FormContextValue<any> | null>;
30
+ export declare function useForm<T extends ArcObjectAny>(): FormContextValue<T>;
28
31
  export declare const Form: <T extends ArcObjectAny>(props: FormProps<T> & React.RefAttributes<FormRef<T>>) => JSX.Element;
29
32
  //# sourceMappingURL=form.d.ts.map
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { createContext as createContext3, useCallback as useCallback3, useContex
6
6
  import {
7
7
  deepMerge
8
8
  } from "@arcote.tech/arc";
9
- import {
9
+ import React, {
10
10
  createContext,
11
11
  forwardRef,
12
12
  useCallback,
@@ -17,6 +17,32 @@ import {
17
17
  } from "react";
18
18
  import { jsx } from "react/jsx-runtime";
19
19
  var FormContext = createContext(null);
20
+ function useForm() {
21
+ const context = React.useContext(FormContext);
22
+ if (!context) {
23
+ throw new Error("useForm must be used within a Form component");
24
+ }
25
+ return context;
26
+ }
27
+ function setNestedValue(obj, path, value) {
28
+ const keys = path.split(".");
29
+ const result = { ...obj };
30
+ let current = result;
31
+ for (let i = 0;i < keys.length - 1; i++) {
32
+ const key = keys[i];
33
+ if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
34
+ current[key] = {};
35
+ } else {
36
+ current[key] = { ...current[key] };
37
+ }
38
+ current = current[key];
39
+ }
40
+ current[keys[keys.length - 1]] = value;
41
+ return result;
42
+ }
43
+ function getNestedValue(obj, path) {
44
+ return path.split(".").reduce((current, key) => current?.[key], obj);
45
+ }
20
46
  var Form = forwardRef(function Form2(props, ref) {
21
47
  const { render, schema, onSubmit, defaults, onUnvalidatedSubmit } = props;
22
48
  const [values, setValues] = useState({});
@@ -38,7 +64,13 @@ var Form = forwardRef(function Form2(props, ref) {
38
64
  return Object.values(errors2).some((result) => result);
39
65
  }, [schema, values]);
40
66
  const validatePartial = useCallback((keys) => {
41
- const partialValues = keys.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {});
67
+ const partialValues = keys.reduce((acc, key) => {
68
+ const value = getNestedValue(values, key);
69
+ if (value !== undefined) {
70
+ return setNestedValue(acc, key, value);
71
+ }
72
+ return acc;
73
+ }, {});
42
74
  const errors2 = schema.validatePartial(partialValues);
43
75
  if (errors2)
44
76
  setErrors((prev) => deepMerge(prev, errors2));
@@ -52,13 +84,19 @@ var Form = forwardRef(function Form2(props, ref) {
52
84
  return Object.values(errors2).some((result) => result);
53
85
  }, [schema, values, dirty]);
54
86
  const setFieldValue = useCallback((field, value) => {
55
- setValues((prev) => ({ ...prev, [field]: value }));
87
+ setValues((prev) => setNestedValue(prev, field, value));
56
88
  }, []);
57
89
  const setFieldDirty = useCallback((field) => {
58
90
  setDirty((prev) => new Set([...prev, field]));
59
91
  }, []);
60
92
  useEffect(() => {
61
- const partialValues = Array.from(dirty).reduce((acc, key) => ({ ...acc, [key]: values[key] }), {});
93
+ const partialValues = Array.from(dirty).reduce((acc, key) => {
94
+ const value = getNestedValue(values, key);
95
+ if (value !== undefined) {
96
+ return setNestedValue(acc, key, value);
97
+ }
98
+ return acc;
99
+ }, {});
62
100
  const errors2 = schema.validatePartial(partialValues);
63
101
  setErrors(errors2);
64
102
  }, [values, dirty]);
@@ -77,14 +115,30 @@ var Form = forwardRef(function Form2(props, ref) {
77
115
  useImperativeHandle(ref, () => ({
78
116
  submit: handleSubmit,
79
117
  getValues: () => values,
80
- validate
118
+ validate,
119
+ setFieldValue
81
120
  }), [handleSubmit, values, validate]);
121
+ const buildFieldsStructure = useCallback((element, fieldName, defaultValue) => {
122
+ const isOptional = element?.toJsonSchema && typeof element.toJsonSchema === "function" && element.parent !== undefined;
123
+ if (isOptional) {
124
+ return buildFieldsStructure(element.parent, fieldName, defaultValue);
125
+ }
126
+ const isObject = element?.entries && typeof element.entries === "function";
127
+ if (isObject) {
128
+ const subFields = Object.fromEntries(element.entries().map(([key, value]) => [
129
+ key.charAt(0).toUpperCase() + key.slice(1),
130
+ buildFieldsStructure(value, fieldName ? `${fieldName}.${key}` : key, defaultValue?.[key])
131
+ ]));
132
+ return FormField(fieldName, defaultValue, subFields);
133
+ }
134
+ return FormField(fieldName, defaultValue);
135
+ }, []);
82
136
  const Fields = useMemo(() => {
83
137
  return Object.fromEntries(schema.entries().map(([key, value]) => [
84
138
  key.charAt(0).toUpperCase() + key.slice(1),
85
- FormField(key, defaults?.[key])
139
+ buildFieldsStructure(value, key, defaults?.[key])
86
140
  ]));
87
- }, [schema, defaults]);
141
+ }, [schema, defaults, buildFieldsStructure]);
88
142
  const contextValue = useMemo(() => ({
89
143
  values,
90
144
  errors,
@@ -167,6 +221,9 @@ function useFormPartField(fieldName) {
167
221
 
168
222
  // form/field.tsx
169
223
  import { jsx as jsx3 } from "react/jsx-runtime";
224
+ function getNestedValue2(obj, path) {
225
+ return path.split(".").reduce((current, key) => current?.[key], obj);
226
+ }
170
227
  var FormFieldContext = createContext3(null);
171
228
  function useFormField() {
172
229
  const context = useContext2(FormFieldContext);
@@ -174,7 +231,7 @@ function useFormField() {
174
231
  throw new Error("useFormField must be used within a FormFieldProvider");
175
232
  return context;
176
233
  }
177
- function FormField(name, defaultValue) {
234
+ function FormField(name, defaultValue, subFields) {
178
235
  return ({ translations, render }) => {
179
236
  const form = useContext2(FormContext);
180
237
  if (!form)
@@ -183,7 +240,7 @@ function FormField(name, defaultValue) {
183
240
  const { values, errors, dirty, isSubmitted, setFieldValue, setFieldDirty } = form;
184
241
  const schemaErrors = errors?.["schema"] || {};
185
242
  const fieldErrors = schemaErrors[name] || false;
186
- const value = values[name] ?? defaultValue ?? "";
243
+ const value = getNestedValue2(values, name) ?? defaultValue ?? "";
187
244
  const isDirty = dirty.has(name);
188
245
  const handleChange = useCallback3((value2) => {
189
246
  if (value2?.target?.value !== undefined) {
@@ -218,7 +275,9 @@ function FormField(name, defaultValue) {
218
275
  onChange: handleChange,
219
276
  name: name.toString(),
220
277
  value,
221
- defaultValue
278
+ defaultValue,
279
+ subFields,
280
+ setFieldValue
222
281
  })
223
282
  }, undefined, false, undefined, this);
224
283
  };
@@ -349,6 +408,8 @@ var reactModel = (arcContext, options) => {
349
408
  const [result, setResult] = useState3(null);
350
409
  const [revalidationTrigger, setRevalidationTrigger] = useState3(0);
351
410
  const unsubscribeRef = useRef(null);
411
+ const queryPromiseRef = useRef(null);
412
+ const resolvePromiseRef = useRef(null);
352
413
  useEffect2(() => {
353
414
  if (cacheKey) {
354
415
  if (!model.__cacheRegistry) {
@@ -357,8 +418,13 @@ var reactModel = (arcContext, options) => {
357
418
  if (!model.__cacheRegistry.has(cacheKey)) {
358
419
  model.__cacheRegistry.set(cacheKey, new Set);
359
420
  }
360
- const revalidateFn = () => {
421
+ const revalidateFn = async () => {
422
+ const promise = new Promise((resolve) => {
423
+ resolvePromiseRef.current = resolve;
424
+ });
425
+ queryPromiseRef.current = promise;
361
426
  setRevalidationTrigger((prev) => prev + 1);
427
+ return promise;
362
428
  };
363
429
  model.__cacheRegistry.get(cacheKey).add(revalidateFn);
364
430
  return () => {
@@ -380,6 +446,10 @@ var reactModel = (arcContext, options) => {
380
446
  const { unsubscribe, result: result2 } = model.subscribe(queryBuilderFn, (newResult) => {
381
447
  setResult(newResult);
382
448
  setLoading(false);
449
+ if (resolvePromiseRef.current) {
450
+ resolvePromiseRef.current();
451
+ resolvePromiseRef.current = null;
452
+ }
383
453
  }, defaultAuthContext);
384
454
  unsubscribeRef.current = unsubscribe;
385
455
  return () => {
@@ -396,13 +466,17 @@ var reactModel = (arcContext, options) => {
396
466
  if (!model) {
397
467
  throw new Error("useRevalidate must be used within a ModelProvider");
398
468
  }
399
- return useCallback4((cacheKey) => {
469
+ return useCallback4(async (cacheKey) => {
400
470
  const registry = model.__cacheRegistry;
401
471
  if (registry && registry.has(cacheKey)) {
402
472
  const revalidators = registry.get(cacheKey);
403
- revalidators?.forEach((revalidateFn) => {
404
- revalidateFn();
405
- });
473
+ if (revalidators) {
474
+ const promises = [];
475
+ revalidators.forEach((revalidateFn) => {
476
+ promises.push(revalidateFn());
477
+ });
478
+ await Promise.all(promises);
479
+ }
406
480
  }
407
481
  }, [model]);
408
482
  },
@@ -441,6 +515,7 @@ export {
441
515
  useFormPartField,
442
516
  useFormPart,
443
517
  useFormField,
518
+ useForm,
444
519
  reactModel,
445
520
  formResolver,
446
521
  FormPartContext,
@@ -452,4 +527,4 @@ export {
452
527
  Form
453
528
  };
454
529
 
455
- //# debugId=5CF15300E5CC460C64756E2164756E21
530
+ //# debugId=93653667973401D564756E2164756E21
@@ -17,5 +17,5 @@ export declare const reactModel: <C extends ArcContextAny>(arcContext: C, option
17
17
  catchErrorCallback: (error: any) => void;
18
18
  }) => import("react/jsx-dev-runtime").JSX.Element | null, ({ children }: {
19
19
  children: React.ReactNode;
20
- }) => import("react/jsx-dev-runtime").JSX.Element, <Q extends IArcQueryBuilder>(queryBuilderFn: UnaryFunction<ReturnType<C["queryBuilder"]>, Q>, dependencies?: any[], cacheKey?: string) => [objectUtil.simplify<ReturnType<Q["toQuery"]>["lastResult"]>, false] | [undefined, true], () => (cacheKey: string) => void, () => ArcContextElementMethodReturnType<C["elements"], "commandClient">, <QueryBuilderFn extends QueryFactoryFunction<C>>(queryBuilderFn: QueryBuilderFn, model?: ModelBase<C> | null) => Promise<QueryBuilderFunctionResult<QueryBuilderFn>>, () => Model<C> | null, (token: string) => ModelBase<C>];
20
+ }) => import("react/jsx-dev-runtime").JSX.Element, <Q extends IArcQueryBuilder>(queryBuilderFn: UnaryFunction<ReturnType<C["queryBuilder"]>, Q>, dependencies?: any[], cacheKey?: string) => [objectUtil.simplify<ReturnType<Q["toQuery"]>["lastResult"]>, false] | [undefined, true], () => (cacheKey: string) => Promise<void>, () => ArcContextElementMethodReturnType<C["elements"], "commandClient">, <QueryBuilderFn extends QueryFactoryFunction<C>>(queryBuilderFn: QueryBuilderFn, model?: ModelBase<C> | null) => Promise<QueryBuilderFunctionResult<QueryBuilderFn>>, () => Model<C> | null, (token: string) => ModelBase<C>];
21
21
  //# sourceMappingURL=reactModel.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-react",
3
3
  "type": "module",
4
- "version": "0.1.8",
4
+ "version": "0.1.10",
5
5
  "private": false,
6
6
  "author": "Przemysław Krasiński [arcote.tech]",
7
7
  "description": "React client for the Arc framework, providing utilities for querying data and executing commands, enhancing the development of reactive and efficient user interfaces.",
@@ -23,7 +23,6 @@
23
23
  "dev": "nodemon --ignore dist -e ts,tsx --exec 'bun run build'"
24
24
  },
25
25
  "dependencies": {
26
- "@arcote.tech/arc": "latest",
27
26
  "react": "^18.3.1",
28
27
  "react-dom": "^18.3.1"
29
28
  },
@@ -37,6 +36,7 @@
37
36
  "typescript": "^5.2.2"
38
37
  },
39
38
  "peerDependencies": {
39
+ "@arcote.tech/arc": "latest",
40
40
  "typescript": "^5.0.0"
41
41
  },
42
42
  "files": [