@arcote.tech/arc-react 0.1.1 → 0.1.3

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.
@@ -1,4 +1,4 @@
1
- import type { ArcElement } from "@arcote.tech/arc/elements/element";
1
+ import type { ArcElement } from "@arcote.tech/arc";
2
2
  import React from "react";
3
3
  export type FormFieldContext = {
4
4
  errors: any;
@@ -16,8 +16,9 @@ type FormFieldProps<E extends ArcElement> = {
16
16
  export type FormFieldData<T> = {
17
17
  onChange: (value: any) => void;
18
18
  value: any;
19
+ defaultValue?: any;
19
20
  name: string;
20
21
  };
21
- export declare function FormField<E extends ArcElement>(name: string): ({ translations, render }: FormFieldProps<E>) => import("react/jsx-dev-runtime").JSX.Element;
22
+ export declare function FormField<E extends ArcElement>(name: string, defaultValue?: any): ({ translations, render }: FormFieldProps<E>) => import("react/jsx-dev-runtime").JSX.Element;
22
23
  export {};
23
24
  //# sourceMappingURL=field.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { type ArcObjectAny } from "@arcote.tech/arc";
2
+ import React from "react";
3
+ export type FormPartContextValue = {
4
+ registerField: (field: string) => void;
5
+ unregisterField: (field: string) => void;
6
+ validatePart: () => boolean;
7
+ };
8
+ export declare const FormPartContext: React.Context<FormPartContextValue | null>;
9
+ export declare function FormPart({ children }: {
10
+ children: React.ReactNode;
11
+ }): import("react/jsx-dev-runtime").JSX.Element;
12
+ export declare namespace FormPart {
13
+ var displayName: string;
14
+ }
15
+ export declare function useFormPart<T extends ArcObjectAny>(): FormPartContextValue;
16
+ export declare function useFormPartField(fieldName: string): FormPartContextValue | null;
17
+ //# sourceMappingURL=form-part.d.ts.map
@@ -1,4 +1,4 @@
1
- import { type ArcObjectAny, type ArcObjectKeys, type ArcObjectValueByKey } from "@arcote.tech/arc";
1
+ import { type $type, type ArcObjectAny, type ArcObjectKeys, type ArcObjectValueByKey } from "@arcote.tech/arc";
2
2
  import React from "react";
3
3
  import { FormField } from "./field";
4
4
  export type FormContextValue<T extends ArcObjectAny> = {
@@ -8,16 +8,22 @@ export type FormContextValue<T extends ArcObjectAny> = {
8
8
  isSubmitted: boolean;
9
9
  setFieldValue: (field: ArcObjectKeys<T>, value: any) => void;
10
10
  setFieldDirty: (field: ArcObjectKeys<T>) => void;
11
+ validatePartial: (keys: ArcObjectKeys<T>[]) => boolean;
11
12
  };
12
- export declare const FormContext: React.Context<FormContextValue<any> | null>;
13
- export declare function Form<T extends ArcObjectAny>({ render, schema, onSubmit, }: {
13
+ export type FormRef<T extends ArcObjectAny> = {
14
+ submit: () => Promise<void>;
15
+ getValues: () => Partial<$type<T>>;
16
+ validate: () => boolean;
17
+ };
18
+ export type FormProps<T extends ArcObjectAny> = {
14
19
  render: (props: {
15
20
  [K in ArcObjectKeys<T> as Capitalize<`${K}`>]: ReturnType<typeof FormField<ArcObjectValueByKey<T, K>>>;
16
- }) => React.ReactNode;
21
+ }, values: Partial<$type<T>>) => React.ReactNode;
17
22
  schema: T;
18
- onSubmit: (values: Record<ArcObjectKeys<T>, any>) => void | Promise<void>;
19
- }): import("react/jsx-dev-runtime").JSX.Element;
20
- export declare namespace Form {
21
- var displayName: string;
22
- }
23
+ onSubmit: (values: $type<T>) => void | Promise<void>;
24
+ onUnvalidatedSubmit?: (values: Partial<$type<T>>, errors: any) => void;
25
+ defaults?: Partial<$type<T>>;
26
+ };
27
+ export declare const FormContext: React.Context<FormContextValue<any> | null>;
28
+ export declare const Form: <T extends ArcObjectAny>(props: FormProps<T> & React.RefAttributes<FormRef<T>>) => JSX.Element;
23
29
  //# sourceMappingURL=form.d.ts.map
@@ -1,5 +1,6 @@
1
1
  export * from "./field";
2
2
  export * from "./form";
3
+ export * from "./form-part";
3
4
  export * from "./legacy_form_resolver";
4
5
  export * from "./message";
5
6
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,99 +1,195 @@
1
1
  'use client';
2
2
  // form/field.tsx
3
- import { createContext as createContext2, useCallback as useCallback2, useContext, useMemo as useMemo2 } from "react";
3
+ import { createContext as createContext3, useCallback as useCallback3, useContext as useContext2, useMemo as useMemo2 } from "react";
4
4
 
5
5
  // form/form.tsx
6
+ import {
7
+ deepMerge
8
+ } from "@arcote.tech/arc";
6
9
  import {
7
10
  createContext,
11
+ forwardRef,
8
12
  useCallback,
9
13
  useEffect,
14
+ useImperativeHandle,
10
15
  useMemo,
11
16
  useState
12
17
  } from "react";
13
18
  import { jsx } from "react/jsx-runtime";
14
19
  var FormContext = createContext(null);
15
- function Form({
16
- render,
17
- schema,
18
- onSubmit
19
- }) {
20
+ var Form = forwardRef(function Form2(props, ref) {
21
+ const { render, schema, onSubmit, defaults, onUnvalidatedSubmit } = props;
20
22
  const [values, setValues] = useState({});
21
23
  const [errors, setErrors] = useState({});
22
24
  const [dirty, setDirty] = useState(new Set);
23
25
  const [isSubmitted, setIsSubmitted] = useState(false);
26
+ useEffect(() => {
27
+ if (defaults) {
28
+ setValues((prev) => ({
29
+ ...prev,
30
+ ...defaults
31
+ }));
32
+ }
33
+ }, [defaults]);
24
34
  const validate = useCallback(() => {
25
35
  const errors2 = schema.validate(values);
26
36
  setErrors(errors2);
37
+ setDirty(new Set(schema.entries().map(([key]) => key)));
27
38
  return Object.values(errors2).some((result) => result);
28
39
  }, [schema, values]);
40
+ const validatePartial = useCallback((keys) => {
41
+ const partialValues = keys.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {});
42
+ const errors2 = schema.validatePartial(partialValues);
43
+ if (errors2)
44
+ setErrors((prev) => deepMerge(prev, errors2));
45
+ setDirty((prev) => {
46
+ const newSet = new Set([...prev, ...keys]);
47
+ if (newSet.size === prev.size && [...prev].every((key) => newSet.has(key))) {
48
+ return prev;
49
+ }
50
+ return newSet;
51
+ });
52
+ return Object.values(errors2).some((result) => result);
53
+ }, [schema, values, dirty]);
29
54
  const setFieldValue = useCallback((field, value) => {
30
55
  setValues((prev) => ({ ...prev, [field]: value }));
31
- }, [schema, isSubmitted]);
32
- useEffect(() => {
33
- if (isSubmitted) {
34
- validate();
35
- }
36
- }, [isSubmitted, values]);
56
+ }, []);
37
57
  const setFieldDirty = useCallback((field) => {
38
58
  setDirty((prev) => new Set([...prev, field]));
39
59
  }, []);
40
- const hasErrors = useMemo(() => Object.values(errors).some((result) => result), [errors]);
60
+ useEffect(() => {
61
+ const partialValues = Array.from(dirty).reduce((acc, key) => ({ ...acc, [key]: values[key] }), {});
62
+ const errors2 = schema.validatePartial(partialValues);
63
+ setErrors(errors2);
64
+ }, [values, dirty]);
41
65
  const handleSubmit = useCallback(async (e) => {
42
- e.preventDefault();
66
+ if (e) {
67
+ e.preventDefault();
68
+ }
43
69
  setIsSubmitted(true);
44
- const hasErrors2 = validate();
45
- if (!hasErrors2) {
70
+ const hasErrors = validate();
71
+ if (!hasErrors) {
46
72
  await onSubmit(values);
73
+ } else {
74
+ onUnvalidatedSubmit?.(values, errors);
47
75
  }
48
- }, [schema, values, onSubmit, hasErrors]);
76
+ }, [schema, values, onSubmit, validate, errors, onUnvalidatedSubmit]);
77
+ useImperativeHandle(ref, () => ({
78
+ submit: handleSubmit,
79
+ getValues: () => values,
80
+ validate
81
+ }), [handleSubmit, values, validate]);
49
82
  const Fields = useMemo(() => {
50
83
  return Object.fromEntries(schema.entries().map(([key, value]) => [
51
84
  key.charAt(0).toUpperCase() + key.slice(1),
52
- FormField(key)
85
+ FormField(key, defaults?.[key])
53
86
  ]));
54
- }, [schema]);
87
+ }, [schema, defaults]);
55
88
  const contextValue = useMemo(() => ({
56
89
  values,
57
90
  errors,
58
91
  dirty,
59
92
  isSubmitted,
60
93
  setFieldValue,
61
- setFieldDirty
62
- }), [values, errors, dirty, isSubmitted, setFieldValue, setFieldDirty]);
94
+ setFieldDirty,
95
+ validatePartial
96
+ }), [
97
+ values,
98
+ errors,
99
+ dirty,
100
+ isSubmitted,
101
+ setFieldValue,
102
+ setFieldDirty,
103
+ validatePartial
104
+ ]);
63
105
  return /* @__PURE__ */ jsx(FormContext.Provider, {
64
106
  value: contextValue,
65
107
  children: /* @__PURE__ */ jsx("form", {
66
108
  onSubmit: handleSubmit,
67
- children: render(Fields)
109
+ children: render(Fields, values)
68
110
  }, undefined, false, undefined, this)
69
111
  }, undefined, false, undefined, this);
112
+ });
113
+
114
+ // form/form-part.tsx
115
+ import React2, { createContext as createContext2, useCallback as useCallback2, useContext, useState as useState2 } from "react";
116
+ import { jsx as jsx2 } from "react/jsx-runtime";
117
+ var FormPartContext = createContext2(null);
118
+ function FormPart({ children }) {
119
+ const formContext = useContext(FormContext);
120
+ if (!formContext) {
121
+ throw new Error("FormPart must be used within a Form");
122
+ }
123
+ const [registeredFields, setRegisteredFields] = useState2(new Set);
124
+ const registerField = useCallback2((field) => {
125
+ setRegisteredFields((prev) => new Set([...prev, field]));
126
+ }, []);
127
+ const unregisterField = useCallback2((field) => {
128
+ setRegisteredFields((prev) => {
129
+ const newSet = new Set(prev);
130
+ newSet.delete(field);
131
+ return newSet;
132
+ });
133
+ }, []);
134
+ const validatePart = useCallback2(() => {
135
+ const { validatePartial } = formContext;
136
+ return validatePartial(Array.from(registeredFields));
137
+ }, [formContext, registeredFields]);
138
+ return /* @__PURE__ */ jsx2(FormPartContext.Provider, {
139
+ value: {
140
+ registerField,
141
+ unregisterField,
142
+ validatePart
143
+ },
144
+ children
145
+ }, undefined, false, undefined, this);
146
+ }
147
+ FormPart.displayName = "FormPart";
148
+ function useFormPart() {
149
+ const context = useContext(FormPartContext);
150
+ if (!context) {
151
+ throw new Error("useFormPart must be used within a FormPart");
152
+ }
153
+ return context;
154
+ }
155
+ function useFormPartField(fieldName) {
156
+ const context = useContext(FormPartContext);
157
+ if (!context)
158
+ return null;
159
+ React2.useEffect(() => {
160
+ context.registerField(fieldName);
161
+ return () => {
162
+ context.unregisterField(fieldName);
163
+ };
164
+ }, [fieldName]);
165
+ return context;
70
166
  }
71
- Form.displayName = "Form";
72
167
 
73
168
  // form/field.tsx
74
- import { jsx as jsx2 } from "react/jsx-runtime";
75
- var FormFieldContext = createContext2(null);
169
+ import { jsx as jsx3 } from "react/jsx-runtime";
170
+ var FormFieldContext = createContext3(null);
76
171
  function useFormField() {
77
- const context = useContext(FormFieldContext);
172
+ const context = useContext2(FormFieldContext);
78
173
  if (!context)
79
174
  throw new Error("useFormField must be used within a FormFieldProvider");
80
175
  return context;
81
176
  }
82
- function FormField(name) {
177
+ function FormField(name, defaultValue) {
83
178
  return ({ translations, render }) => {
84
- const form = useContext(FormContext);
179
+ const form = useContext2(FormContext);
85
180
  if (!form)
86
181
  throw new Error("FormField must be used within a Form");
182
+ useFormPartField(name);
87
183
  const { values, errors, dirty, isSubmitted, setFieldValue, setFieldDirty } = form;
88
184
  const schemaErrors = errors?.["schema"] || {};
89
185
  const fieldErrors = schemaErrors[name] || false;
90
- const value = values[name] || "";
186
+ const value = values[name] ?? defaultValue ?? "";
91
187
  const isDirty = dirty.has(name);
92
- const handleChange = useCallback2((value2) => {
93
- if (typeof value2 === "string") {
94
- setFieldValue(name, value2);
95
- } else if (value2?.target?.value !== undefined) {
188
+ const handleChange = useCallback3((value2) => {
189
+ if (value2?.target?.value !== undefined) {
96
190
  setFieldValue(name, value2.target.value);
191
+ } else {
192
+ setFieldValue(name, value2);
97
193
  }
98
194
  if (!isDirty) {
99
195
  setFieldDirty(name);
@@ -116,12 +212,13 @@ function FormField(name) {
116
212
  errors: isSubmitted ? fieldErrors : false,
117
213
  messages: errorMessages
118
214
  }), [fieldErrors, isSubmitted, errorMessages]);
119
- return /* @__PURE__ */ jsx2(FormFieldContext.Provider, {
215
+ return /* @__PURE__ */ jsx3(FormFieldContext.Provider, {
120
216
  value: contextValue,
121
217
  children: render({
122
218
  onChange: handleChange,
123
219
  name: name.toString(),
124
- value
220
+ value,
221
+ defaultValue
125
222
  })
126
223
  }, undefined, false, undefined, this);
127
224
  };
@@ -136,12 +233,12 @@ function formResolver(schema) {
136
233
  };
137
234
  }
138
235
  // form/message.tsx
139
- import { jsx as jsx3 } from "react/jsx-runtime";
236
+ import { jsx as jsx4 } from "react/jsx-runtime";
140
237
  function FormMessage({ ...props }) {
141
238
  const { messages } = useFormField();
142
239
  if (messages.length === 0)
143
240
  return null;
144
- return /* @__PURE__ */ jsx3("span", {
241
+ return /* @__PURE__ */ jsx4("span", {
145
242
  ...props,
146
243
  children: messages[0]
147
244
  }, undefined, false, undefined, this);
@@ -155,12 +252,13 @@ import {
155
252
  rtcClientFactory
156
253
  } from "@arcote.tech/arc";
157
254
  import {
158
- createContext as createContext3,
159
- useContext as useContext2,
255
+ createContext as createContext4,
256
+ useCallback as useCallback4,
257
+ useContext as useContext3,
160
258
  useEffect as useEffect2,
161
259
  useMemo as useMemo3,
162
260
  useRef,
163
- useState as useState2
261
+ useState as useState3
164
262
  } from "react";
165
263
 
166
264
  // sqliteWasmAdapter.ts
@@ -174,19 +272,22 @@ var sqliteWasmAdapterFactory = (db) => {
174
272
  };
175
273
 
176
274
  // reactModel.tsx
177
- import { jsx as jsx4 } from "react/jsx-runtime";
275
+ import { jsx as jsx5 } from "react/jsx-runtime";
178
276
  var reactModel = (arcContextPromise, options) => {
179
- const LiveModelContext = createContext3(null);
180
- const LocalModelContext = createContext3(null);
277
+ const LiveModelContext = createContext4(null);
278
+ const LocalModelContext = createContext4(null);
181
279
  let masterModel = null;
182
280
  return [
183
281
  function LiveModelProvider(props) {
184
- const [context, setContext] = useState2(null);
282
+ const [context, setContext] = useState3(null);
185
283
  useEffect2(() => {
186
284
  arcContextPromise.then((arcContext) => {
187
285
  setContext(arcContext);
188
286
  });
189
287
  }, [arcContextPromise]);
288
+ useEffect2(() => {
289
+ model?.setAuthToken(props.token);
290
+ }, [props.token]);
190
291
  const model = useMemo3(() => {
191
292
  if (typeof window === "undefined")
192
293
  return null;
@@ -201,13 +302,13 @@ var reactModel = (arcContextPromise, options) => {
201
302
  } else {
202
303
  const dbAdapterPromise = sqliteWasmAdapterFactory(options.sqliteAdapter)(context);
203
304
  const dataStorage = new MasterDataStorage(dbAdapterPromise, rtcClientFactory(props.token), context);
204
- const model2 = new Model(context, dataStorage, props.client, props.catchErrorCallback);
305
+ const model2 = new Model(context, dataStorage, props.catchErrorCallback);
205
306
  masterModel = model2;
206
307
  return model2;
207
308
  }
208
309
  }, [context, options, props.client]);
209
- const [syncProgress, setSyncProgress] = useState2([]);
210
- const [syncDone, setSyncDone] = useState2(false);
310
+ const [syncProgress, setSyncProgress] = useState3([]);
311
+ const [syncDone, setSyncDone] = useState3(false);
211
312
  useEffect2(() => {
212
313
  if (typeof window === "undefined" || !model)
213
314
  return;
@@ -222,81 +323,128 @@ var reactModel = (arcContextPromise, options) => {
222
323
  sync();
223
324
  }, [model]);
224
325
  if (!model || !syncDone)
225
- return props.syncView ? /* @__PURE__ */ jsx4(props.syncView, {
326
+ return props.syncView ? /* @__PURE__ */ jsx5(props.syncView, {
226
327
  progress: syncProgress
227
328
  }, undefined, false, undefined, this) : null;
228
- return /* @__PURE__ */ jsx4(LiveModelContext.Provider, {
329
+ return /* @__PURE__ */ jsx5(LiveModelContext.Provider, {
229
330
  value: model,
230
331
  children: props.children
231
332
  }, undefined, false, undefined, this);
232
333
  },
233
334
  function LocalModelProvider({ children }) {
234
- const parentModel = useContext2(LiveModelContext);
335
+ const parentModel = useContext3(LiveModelContext);
235
336
  if (!parentModel || !(parentModel instanceof Model)) {
236
337
  throw new Error("LocalModelProvider must be used within a LiveModelProvider");
237
338
  }
238
- const [localModel] = useState2(() => parentModel.fork());
239
- return /* @__PURE__ */ jsx4(LocalModelContext.Provider, {
339
+ const [localModel] = useState3(() => parentModel);
340
+ return /* @__PURE__ */ jsx5(LocalModelContext.Provider, {
240
341
  value: localModel,
241
342
  children
242
343
  }, undefined, false, undefined, this);
243
344
  },
244
- function useQuery(queryBuilderFn, dependencies = []) {
245
- const model = useContext2(LocalModelContext) || useContext2(LiveModelContext);
345
+ function useQuery(queryBuilderFn, dependencies = [], cacheKey) {
346
+ const model = useContext3(LocalModelContext) || useContext3(LiveModelContext);
246
347
  if (!model) {
247
348
  throw new Error("useQuery must be used within a ModelProvider");
248
349
  }
249
- const [result, setResult] = useState2(null);
250
- const [loading, setLoading] = useState2(true);
350
+ const [result, setResult] = useState3(null);
351
+ const [revalidationTrigger, setRevalidationTrigger] = useState3(0);
251
352
  const unsubscribeRef = useRef(null);
353
+ useEffect2(() => {
354
+ if (cacheKey) {
355
+ if (!model.__cacheRegistry) {
356
+ model.__cacheRegistry = new Map;
357
+ }
358
+ if (!model.__cacheRegistry.has(cacheKey)) {
359
+ model.__cacheRegistry.set(cacheKey, new Set);
360
+ }
361
+ const revalidateFn = () => {
362
+ setRevalidationTrigger((prev) => prev + 1);
363
+ };
364
+ model.__cacheRegistry.get(cacheKey).add(revalidateFn);
365
+ return () => {
366
+ const registry = model.__cacheRegistry?.get(cacheKey);
367
+ if (registry) {
368
+ registry.delete(revalidateFn);
369
+ if (registry.size === 0) {
370
+ model.__cacheRegistry.delete(cacheKey);
371
+ }
372
+ }
373
+ };
374
+ }
375
+ }, [model, cacheKey]);
252
376
  useEffect2(() => {
253
377
  if (unsubscribeRef.current) {
254
378
  unsubscribeRef.current();
255
379
  }
380
+ const defaultAuthContext = { userId: "react-user", roles: ["user"] };
256
381
  const { unsubscribe, result: result2 } = model.subscribe(queryBuilderFn, (newResult) => {
257
382
  setResult(newResult);
258
- setLoading(false);
259
- });
383
+ }, defaultAuthContext);
260
384
  unsubscribeRef.current = unsubscribe;
261
- result2.then(() => {
262
- setLoading(false);
263
- });
264
385
  return () => {
265
386
  if (unsubscribeRef.current) {
266
387
  unsubscribeRef.current();
267
388
  unsubscribeRef.current = null;
268
389
  }
269
390
  };
270
- }, [model, ...dependencies]);
271
- return [result, loading];
391
+ }, [model, revalidationTrigger, ...dependencies]);
392
+ return [result];
393
+ },
394
+ function useRevalidate() {
395
+ const model = useContext3(LocalModelContext) || useContext3(LiveModelContext);
396
+ if (!model) {
397
+ throw new Error("useRevalidate must be used within a ModelProvider");
398
+ }
399
+ return useCallback4((cacheKey) => {
400
+ const registry = model.__cacheRegistry;
401
+ if (registry && registry.has(cacheKey)) {
402
+ const revalidators = registry.get(cacheKey);
403
+ revalidators?.forEach((revalidateFn) => {
404
+ revalidateFn();
405
+ });
406
+ }
407
+ }, [model]);
272
408
  },
273
409
  function useCommands() {
274
- const model = useContext2(LocalModelContext) || useContext2(LiveModelContext);
410
+ const model = useContext3(LocalModelContext) || useContext3(LiveModelContext);
275
411
  if (!model) {
276
412
  throw new Error("useCommands must be used within a ModelProvider");
277
413
  }
278
- return model.commands();
414
+ const defaultAuthContext = { userId: "react-user", roles: ["user"] };
415
+ return model.commands(defaultAuthContext);
279
416
  },
280
417
  async function query(queryBuilderFn, model) {
281
418
  if (!model)
282
419
  model = masterModel;
283
420
  if (!model)
284
421
  throw new Error("Model not found");
285
- return model.query(queryBuilderFn);
422
+ const defaultAuthContext = { userId: "react-user", roles: ["user"] };
423
+ return model.query(queryBuilderFn, defaultAuthContext);
286
424
  },
287
425
  function useLocalModel() {
288
- const model = useContext2(LocalModelContext);
426
+ const model = useContext3(LocalModelContext);
289
427
  if (!model) {
290
428
  return null;
291
429
  }
292
430
  return model;
431
+ },
432
+ function useModel(token) {
433
+ const parentModel = useContext3(LiveModelContext);
434
+ if (!parentModel)
435
+ throw new Error("useModel have to be used in Provider");
436
+ return parentModel;
293
437
  }
294
438
  ];
295
439
  };
296
440
  export {
441
+ useFormPartField,
442
+ useFormPart,
297
443
  useFormField,
298
444
  reactModel,
299
445
  formResolver,
446
+ FormPartContext,
447
+ FormPart,
300
448
  FormMessage,
301
449
  FormFieldContext,
302
450
  FormField,
@@ -304,4 +452,4 @@ export {
304
452
  Form
305
453
  };
306
454
 
307
- //# debugId=1DFCDB435242322064756E2164756E21
455
+ //# debugId=8ECE7A0A6915EECC64756E2164756E21