@embeddables/forms 0.0.5 → 0.2.1

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,27 +1,4 @@
1
1
  import { EmbeddablesInstance } from "@embeddables/core";
2
- //#region src/errors.d.ts
3
- /**
4
- * Typed error hierarchy. Every failure this SDK raises on its own behalf is an
5
- * instance of one of these, so consumers branch on the type
6
- * (`if (e instanceof SchemaError) …`) instead of string-matching messages.
7
- * Catch `FormsError` to handle them all.
8
- *
9
- * An error thrown by a consumer's own custom validator is never wrapped in one
10
- * of these — it propagates with its original type and stack.
11
- */
12
- /** Base class for every error the SDK throws. */
13
- declare class FormsError extends Error {
14
- constructor(message: string, options?: ErrorOptions);
15
- }
16
- /** The schema is malformed. */
17
- declare class SchemaError extends FormsError {
18
- constructor(message: string, options?: ErrorOptions);
19
- }
20
- /** A custom validator returned a thenable, or a shape that is not a message. */
21
- declare class ValidatorError extends FormsError {
22
- constructor(message: string, options?: ErrorOptions);
23
- }
24
- //#endregion
25
2
  //#region ../shared-types/dist/json.types.d.ts
26
3
  /**
27
4
  * Recursive JSON value, compatible with JSONB column values and public
@@ -31,8 +8,54 @@ type JsonValue = string | number | boolean | null | JsonValue[] | {
31
8
  [key: string]: JsonValue;
32
9
  };
33
10
  //#endregion
11
+ //#region ../shared-types/dist/analytics-instance.types.d.ts
12
+ /**
13
+ * Hono-free analytics client surface other SDKs accept without depending on
14
+ * `hono` or `@embeddables/analytics`. Precise ingest event types stay inferred
15
+ * from the Worker in analytics-sdk; this module is the structural instance, not
16
+ * the HTTP contract.
17
+ *
18
+ * `trackEvent` is a method (not a function property) so parameter checking
19
+ * stays bivariant: a precise analytics-sdk client remains assignable here, and
20
+ * callers without those types can still pass their payloads.
21
+ */
22
+ /** Loose event payload other SDKs pass to `trackEvent`. */
23
+ interface AnalyticsTrackEvent {
24
+ event_name: string;
25
+ [key: string]: unknown;
26
+ }
27
+ /** Body `trackEvent` resolves with — identity plus ingest outcome. */
28
+ interface AnalyticsTrackResult {
29
+ app_user_id: string;
30
+ accepted: number;
31
+ forwarded: boolean;
32
+ }
33
+ /**
34
+ * Structural stand-in for `ReturnType<typeof initAnalytics>`. Other SDKs type
35
+ * injected clients against this; analytics-sdk's `AnalyticsClient` must remain
36
+ * assignable to it.
37
+ */
38
+ interface AnalyticsInstance<TEvent = AnalyticsTrackEvent> {
39
+ trackEvent(input: TEvent | readonly TEvent[]): Promise<AnalyticsTrackResult>;
40
+ /** Live identity: the composed core instance's current user. */
41
+ getAppUserId(): string | null;
42
+ getProjectId(): string;
43
+ }
44
+ //#endregion
45
+ //#region ../shared-types/dist/forms.types.d.ts
46
+ /** Stored field value for `type: file` — bytes live in R2; JSON carries metadata only. */
47
+ type FormFileRef = {
48
+ file_id: string;
49
+ name: string;
50
+ content_type: string;
51
+ size: number;
52
+ status: 'uploading' | 'done' | 'error';
53
+ uploaded_at?: string;
54
+ error?: string;
55
+ } & Record<string, JsonValue>;
56
+ //#endregion
34
57
  //#region src/core/config.d.ts
35
- type FieldType = 'text' | 'email' | 'number' | 'boolean' | 'select' | 'multiselect' | 'json';
58
+ type FieldType = 'text' | 'email' | 'number' | 'boolean' | 'select' | 'multiselect' | 'json' | 'file';
36
59
  /** Runtime `type` literal → the TypeScript type of that field's value. */
37
60
  interface ValueOfFieldType {
38
61
  text: string;
@@ -42,22 +65,43 @@ interface ValueOfFieldType {
42
65
  select: string;
43
66
  multiselect: string[];
44
67
  json: JsonValue;
68
+ file: FormFileRef | null;
45
69
  }
46
70
  type FieldValidator<TValue extends JsonValue = JsonValue> = (args: {
47
71
  value: TValue;
48
72
  values: Readonly<Record<string, JsonValue>>;
49
73
  }) => string | readonly string[] | null;
74
+ /** One declarable choice on a `select` or `multiselect` field. */
75
+ interface FieldOption {
76
+ readonly value: string;
77
+ readonly label?: string;
78
+ /**
79
+ * `multiselect` only. A selected exclusive option cannot coexist with any
80
+ * other value: selecting it clears the rest, and selecting a regular
81
+ * option clears it.
82
+ */
83
+ readonly exclusive?: boolean;
84
+ }
85
+ type SelectFieldOption = Omit<FieldOption, 'exclusive'> & {
86
+ readonly exclusive?: never;
87
+ };
50
88
  interface FieldValidationsFor<TType extends FieldType> {
51
89
  readonly required?: boolean;
52
90
  readonly minLength?: number;
53
91
  readonly maxLength?: number;
54
92
  readonly min?: number;
55
93
  readonly max?: number;
56
- /** ECMAScript source without delimiters. */
94
+ /**
95
+ * ECMAScript source without delimiters. Flags go in an optional leading
96
+ * `(?flags)` prefix, e.g. `'(?u)^\\p{L}+$'`. Allowed: `dgimsuvy`; `g` and
97
+ * `y` are stripped before compilation.
98
+ */
57
99
  readonly pattern?: string;
58
- /** Flags for `pattern`. `g` and `y` are stripped before compilation. */
59
- readonly patternFlags?: string;
60
100
  readonly oneOf?: readonly JsonValue[];
101
+ /** MIME types or `type/*` wildcards. */
102
+ readonly accept?: TType extends 'file' ? readonly string[] : never;
103
+ /** Max bytes. */
104
+ readonly maxSize?: TType extends 'file' ? number : never;
61
105
  /** `value` is bound to this field's declared `type`. */
62
106
  readonly custom?: FieldValidator<ValueOfFieldType[TType]>;
63
107
  }
@@ -65,9 +109,16 @@ interface FieldConfigFor<TType extends FieldType> {
65
109
  readonly key: string;
66
110
  readonly label: string;
67
111
  readonly type: TType;
112
+ /**
113
+ * Declarable choices. `select` accepts one of these values, `multiselect`
114
+ * accepts any number of them. Typed as `never` on every other field type.
115
+ */
116
+ readonly options?: TType extends 'select' ? readonly SelectFieldOption[] : TType extends 'multiselect' ? readonly FieldOption[] : never;
68
117
  readonly validations?: FieldValidationsFor<TType>;
69
118
  readonly registryId?: string;
70
119
  readonly protocolFieldId?: string;
120
+ /** When true, the field value is persisted to a per-form browser cookie on `set`. */
121
+ readonly includeInCookies?: boolean;
71
122
  }
72
123
  type FieldConfig = { [T in FieldType]: FieldConfigFor<T>; }[FieldType];
73
124
  type FieldValidations = { [T in FieldType]: FieldValidationsFor<T>; }[FieldType];
@@ -84,90 +135,6 @@ type ProtocolFieldId<TSchema extends FormSchema> = [TSchema] extends [FormSchema
84
135
  protocolFieldId: string;
85
136
  }>['protocolFieldId'];
86
137
  //#endregion
87
- //#region src/storage/storage.d.ts
88
- /** Every form on the origin shares this one entry, indexed by form ID. */
89
- declare const FORM_DATA_KEY = "EMBEDDABLES-FORM-DATA";
90
- interface FormsStorage {
91
- getItem(key: string): string | null;
92
- setItem(key: string, value: string): void;
93
- removeItem(key: string): void;
94
- }
95
- //#endregion
96
- //#region ../shared-types/dist/analytics-instance.types.d.ts
97
- /**
98
- * Hono-free analytics client surface other SDKs accept without depending on
99
- * `hono` or `@embeddables/analytics`. Precise ingest event types stay inferred
100
- * from the Worker in analytics-sdk; this module is the structural instance, not
101
- * the HTTP contract.
102
- *
103
- * `trackEvent` is a method (not a function property) so parameter checking
104
- * stays bivariant: a precise analytics-sdk client remains assignable here, and
105
- * callers without those types can still pass their payloads.
106
- */
107
- /** Loose event payload other SDKs pass to `trackEvent`. */
108
- interface AnalyticsTrackEvent {
109
- event_name: string;
110
- [key: string]: unknown;
111
- }
112
- /** Body `trackEvent` resolves with — identity plus ingest outcome. */
113
- interface AnalyticsTrackResult {
114
- app_user_id: string;
115
- accepted: number;
116
- forwarded: boolean;
117
- }
118
- /**
119
- * Structural stand-in for `ReturnType<typeof initAnalytics>`. Other SDKs type
120
- * injected clients against this; analytics-sdk's `AnalyticsClient` must remain
121
- * assignable to it.
122
- */
123
- interface AnalyticsInstance<TEvent = AnalyticsTrackEvent> {
124
- trackEvent(input: TEvent | readonly TEvent[]): Promise<AnalyticsTrackResult>;
125
- /** Live identity: the composed core instance's current user. */
126
- getAppUserId(): string | null;
127
- getProjectId(): string;
128
- }
129
- //#endregion
130
- //#region ../shared-types/dist/analytics-ingest.types.d.ts
131
- type FieldUpdatedType = 'text' | 'email' | 'number' | 'boolean' | 'select' | 'multiselect' | 'json';
132
- /**
133
- * Raw `field_value` accepted by ingest. Every forms-sdk `ValueOfFieldType` is a
134
- * JSON value — scalar (`text`/`email`/`select` → string, `number` → number,
135
- * `boolean` → boolean), `multiselect` → `string[]`, `json` → arbitrary JSON —
136
- * so the contract collapses to `JsonValue`; the runtime ingest schema is what
137
- * bounds each shape.
138
- */
139
- type FieldUpdatedValue = JsonValue;
140
- type FunnelStepFields = {
141
- is_funnel_step?: boolean;
142
- funnel_step_label?: string;
143
- };
144
- type FieldUpdatedEvent = FunnelStepFields & {
145
- event_name: 'field:updated';
146
- /** Schema key of the updated field. */
147
- field_key: string;
148
- field_type: FieldUpdatedType;
149
- field_value?: FieldUpdatedValue;
150
- /** Field Registry identifier when the field is registry-backed. */
151
- registry_field_id?: string;
152
- /** Protocol question identifier when the field is protocol-backed. */
153
- protocol_field_id?: string;
154
- };
155
- type DataUpdatedEntry = {
156
- value: string;
157
- label: string;
158
- };
159
- type DataUpdatedEvent = FunnelStepFields & {
160
- event_name: 'data:updated';
161
- data: Record<string, DataUpdatedEntry>;
162
- };
163
- type FormSubmittedEvent = FunnelStepFields & {
164
- event_name: 'form:submitted';
165
- form_key: string;
166
- };
167
- //#endregion
168
- //#region src/core/analytics.d.ts
169
- type FormsAnalyticsEvent = DataUpdatedEvent | FieldUpdatedEvent | FormSubmittedEvent;
170
- //#endregion
171
138
  //#region src/core/form.d.ts
172
139
  /** Per-key validation errors. An empty object means the operation succeeded. */
173
140
  type FieldErrors<TSchema extends FormSchema> = Readonly<Partial<Record<FormFieldKey<TSchema>, readonly string[]>>>;
@@ -229,16 +196,43 @@ interface FormInstance<TSchema extends FormSchema> {
229
196
  validate(patch?: Partial<FormValues<TSchema>>): Promise<ValidateResult<TSchema>>;
230
197
  errors(): FieldErrors<TSchema>;
231
198
  clear(): void;
199
+ /**
200
+ * Uploads a file for a declared `type: file` field. Returns a `FormFileRef`
201
+ * with `status: 'done'`; the caller commits it with `.set()`.
202
+ */
203
+ uploadFile(args: {
204
+ key: FormFieldKey<TSchema>;
205
+ file: Blob;
206
+ fileName?: string;
207
+ }): Promise<FormFileRef>;
232
208
  /** Synchronous listener for value and error mutations. Returns an unsubscribe function. */
233
209
  subscribe(listener: () => void): () => void;
234
210
  }
235
211
  /** Every form the app can open, keyed by form id — the shape `_dist` exports. */
236
212
  type FormSchemaMap = Readonly<Record<string, FormSchema>>;
237
213
  type CustomValidationsFor<TSchema extends FormSchema> = { readonly [K in FormFieldKey<TSchema>]?: FieldValidator; };
214
+ interface FormCustomValidationsEntry<TSchema extends FormSchema = FormSchema> {
215
+ formId: Extract<TSchema['id'], string>;
216
+ customValidations: CustomValidationsFor<TSchema>;
217
+ }
218
+ /** Per-form SSR/hydration seed passed from `initFormsServer().getServerFormData()`. */
219
+ interface FormServerDataEntry<TSchema extends FormSchema = FormSchema> {
220
+ formId: Extract<TSchema['id'], string>;
221
+ serverFormData: Partial<FormValues<TSchema>>;
222
+ }
223
+ type FormCustomValidationsEntriesFor<TSchemas extends FormSchemaMap> = ReadonlyArray<{ [K in keyof TSchemas & string]: FormCustomValidationsEntry<TSchemas[K]>; }[keyof TSchemas & string]>;
224
+ type FormServerDataEntriesFor<TSchemas extends FormSchemaMap> = ReadonlyArray<{ [K in keyof TSchemas & string]: FormServerDataEntry<TSchemas[K]>; }[keyof TSchemas & string]>;
225
+ interface FormsServerInitOptions<TSchemas extends FormSchemaMap = FormSchemaMap> {
226
+ server: EmbeddablesInstance;
227
+ customValidations?: FormCustomValidationsEntriesFor<TSchemas>;
228
+ analyticsInstance?: AnalyticsInstance;
229
+ }
230
+ type ServerFormInstance<TSchema extends FormSchema> = Pick<FormInstance<TSchema>, 'key' | 'set' | 'get' | 'getAll' | 'getValueByProtocolFieldId'>;
231
+ type ServerFormDataByFormId<TSchemas extends FormSchemaMap> = { [K in keyof TSchemas]?: Partial<FormValues<TSchemas[K]>>; };
238
232
  interface InitFormsOptions<TSchemas extends FormSchemaMap = FormSchemaMap> {
239
233
  core: EmbeddablesInstance;
240
- /** Keyed by form id; each schema's own `id` must equal its key. */
241
- schemas: TSchemas;
234
+ customValidations?: FormCustomValidationsEntriesFor<TSchemas>;
235
+ serverFormData?: FormServerDataEntriesFor<TSchemas>;
242
236
  analyticsInstance?: AnalyticsInstance;
243
237
  }
244
238
  interface FormsClient<TSchemas extends FormSchemaMap = FormSchemaMap> {
@@ -250,20 +244,18 @@ interface FormsClient<TSchemas extends FormSchemaMap = FormSchemaMap> {
250
244
  * would not observe each other's writes and the later one's `.set()` would
251
245
  * overwrite the earlier one's storage, so the client never hands out a second.
252
246
  *
253
- * ! `customValidations` therefore belong to the first call. A later call that
254
- * ! passes different validators gets the instance that already exists, built
255
- * ! with the original ones.
247
+ * ! `customValidations` belong to the matching form id on Core. `getForm`
248
+ * ! only selects which memoized instance to return.
256
249
  */
257
250
  getForm<K extends keyof TSchemas & string>(params: {
258
251
  formId: K;
259
- customValidations?: CustomValidationsFor<TSchemas[K]>;
260
252
  }): FormInstance<TSchemas[K]>;
261
253
  }
262
254
  /**
263
- * Validates the core instance and the schema map once, then returns a client
264
- * whose `getForm` builds each form by id.
255
+ * Validates the core instance and registered form schemas once, then returns a
256
+ * client whose `getForm` builds each form by id.
265
257
  */
266
- declare function initForms<const TSchemas extends FormSchemaMap>(options: InitFormsOptions<TSchemas>): FormsClient<TSchemas>;
258
+ declare function initForms<TSchemas extends FormSchemaMap = FormSchemaMap>(options: InitFormsOptions<TSchemas>): FormsClient<TSchemas>;
267
259
  //#endregion
268
- export { FormsError as A, FieldValidations as C, FormValues as D, FormSchema as E, ValidatorError as M, ProtocolFieldId as O, FieldType as S, FormFieldKey as T, AnalyticsTrackEvent as _, FormsClient as a, FormsStorage as b, SubmitResult as c, FormsAnalyticsEvent as d, DataUpdatedEvent as f, AnalyticsInstance as g, FormSubmittedEvent as h, FormSchemaMap as i, SchemaError as j, JsonValue as k, ValidateResult as l, FieldUpdatedType as m, FieldErrors as n, InitFormsOptions as o, FieldUpdatedEvent as p, FormInstance as r, SetResult as s, CustomValidationsFor as t, initForms as u, AnalyticsTrackResult as v, FieldValidator as w, FieldConfig as x, FORM_DATA_KEY as y };
269
- //# sourceMappingURL=index-qfBhdk5L.d.ts.map
260
+ export { JsonValue as A, FormValues as C, AnalyticsInstance as D, FormFileRef as E, AnalyticsTrackEvent as O, FormSchema as S, SelectFieldOption as T, FieldOption as _, FormSchemaMap as a, FieldValidator as b, FormsServerInitOptions as c, ServerFormInstance as d, SetResult as f, FieldConfig as g, initForms as h, FormInstance as i, AnalyticsTrackResult as k, InitFormsOptions as l, ValidateResult as m, FieldErrors as n, FormServerDataEntry as o, SubmitResult as p, FormCustomValidationsEntry as r, FormsClient as s, CustomValidationsFor as t, ServerFormDataByFormId as u, FieldType as v, ProtocolFieldId as w, FormFieldKey as x, FieldValidations as y };
261
+ //# sourceMappingURL=form-x4EJcR9A.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-x4EJcR9A.d.ts","names":[],"sources":["../../shared-types/dist/json.types.d.ts","../../shared-types/dist/analytics-instance.types.d.ts","../../shared-types/dist/forms.types.d.ts","../src/core/config.ts","../src/core/form.ts"],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { A as JsonValue } from "./form-x4EJcR9A.js";
2
+ //#region src/errors.d.ts
3
+ /**
4
+ * Typed error hierarchy. Every failure this SDK raises on its own behalf is an
5
+ * instance of one of these, so consumers branch on the type
6
+ * (`if (e instanceof SchemaError) …`) instead of string-matching messages.
7
+ * Catch `FormsError` to handle them all.
8
+ *
9
+ * An error thrown by a consumer's own custom validator is never wrapped in one
10
+ * of these — it propagates with its original type and stack.
11
+ */
12
+ /** Base class for every error the SDK throws. */
13
+ declare class FormsError extends Error {
14
+ constructor(message: string, options?: ErrorOptions);
15
+ }
16
+ /** The schema is malformed. */
17
+ declare class SchemaError extends FormsError {
18
+ constructor(message: string, options?: ErrorOptions);
19
+ }
20
+ /** A custom validator returned a thenable, or a shape that is not a message. */
21
+ declare class ValidatorError extends FormsError {
22
+ constructor(message: string, options?: ErrorOptions);
23
+ }
24
+ //#endregion
25
+ //#region ../shared-types/dist/analytics-ingest.types.d.ts
26
+ type FieldUpdatedType = 'text' | 'email' | 'number' | 'boolean' | 'select' | 'multiselect' | 'json' | 'file';
27
+ /**
28
+ * Raw `field_value` accepted by ingest. Every forms-sdk `ValueOfFieldType` is a
29
+ * JSON value — scalar (`text`/`email`/`select` → string, `number` → number,
30
+ * `boolean` → boolean), `multiselect` → `string[]`, `json` → arbitrary JSON —
31
+ * so the contract collapses to `JsonValue`; the runtime ingest schema is what
32
+ * bounds each shape.
33
+ */
34
+ type FieldUpdatedValue = JsonValue;
35
+ type FunnelStepFields = {
36
+ is_funnel_step?: boolean;
37
+ funnel_step_label?: string;
38
+ };
39
+ type FieldUpdatedEvent = FunnelStepFields & {
40
+ event_name: 'field:updated';
41
+ /** Schema key of the updated field. */
42
+ field_key: string;
43
+ field_type: FieldUpdatedType;
44
+ field_value?: FieldUpdatedValue;
45
+ /** Field Registry identifier when the field is registry-backed. */
46
+ registry_field_id?: string;
47
+ /** Protocol question identifier when the field is protocol-backed. */
48
+ protocol_field_id?: string;
49
+ form_id: string;
50
+ };
51
+ type DataUpdatedEntry = {
52
+ value: string;
53
+ label: string;
54
+ };
55
+ type DataUpdatedEvent = FunnelStepFields & {
56
+ event_name: 'data:updated';
57
+ data: Record<string, DataUpdatedEntry>;
58
+ form_id: string;
59
+ };
60
+ type FormSubmittedEvent = FunnelStepFields & {
61
+ event_name: 'form:submitted';
62
+ form_key: string;
63
+ };
64
+ //#endregion
65
+ //#region src/storage/storage.d.ts
66
+ /** Every form on the origin shares this one entry, indexed by form ID. */
67
+ declare const FORM_DATA_KEY = "EMBEDDABLES-FORM-DATA";
68
+ interface FormsStorage {
69
+ getItem(key: string): string | null;
70
+ setItem(key: string, value: string): void;
71
+ removeItem(key: string): void;
72
+ }
73
+ //#endregion
74
+ //#region src/core/analytics.d.ts
75
+ type FormsAnalyticsEvent = DataUpdatedEvent | FieldUpdatedEvent | FormSubmittedEvent;
76
+ //#endregion
77
+ export { FieldUpdatedEvent as a, FormsError as c, DataUpdatedEvent as i, SchemaError as l, FORM_DATA_KEY as n, FieldUpdatedType as o, FormsStorage as r, FormSubmittedEvent as s, FormsAnalyticsEvent as t, ValidatorError as u };
78
+ //# sourceMappingURL=index-CmcgIxTH.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CmcgIxTH.d.ts","names":[],"sources":["../src/errors.ts","../../shared-types/dist/analytics-ingest.types.d.ts","../src/storage/storage.ts","../src/core/analytics.ts"],"mappings":";;;;;;;;;;;;cAWa,mBAAmB;EAClB,YAAA,iBAAiB,UAAU;;;cAO5B,oBAAoB;EACnB,YAAA,iBAAiB,UAAU;;;cAO5B,uBAAuB;EACtB,YAAA,iBAAiB,UAAU;;;;KCtB7B;;;;;;;;KAQA,oBAAoB;KACpB;EACR;EACA;;KA4CQ,oBAAoB;EAC5B;;EAEA;EACA,YAAY;EACZ,cAAc;;EAEd;;EAEA;EACA;;KAEQ;EACR;EACA;;KAEQ,mBAAmB;EAC3B;EACA,MAAM,eAAe;EACrB;;KAEQ,qBAAqB;EAC7B;EACA;;;;;cC/ES;UAEI;EACf,QAAQ;EACR,QAAQ,aAAa;EACrB,WAAW;;;;KCkBD,sBAAsB,mBAAmB,oBAAoB"}
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_form = require("./form-DzfCc5X2.cjs");
2
+ const require_form = require("./form-CdIfHqNc.cjs");
3
3
  exports.FORM_DATA_KEY = require_form.FORM_DATA_KEY;
4
4
  exports.FormsError = require_form.FormsError;
5
5
  exports.SchemaError = require_form.SchemaError;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { A as FormsError, C as FieldValidations, D as FormValues, E as FormSchema, M as ValidatorError, O as ProtocolFieldId, S as FieldType, T as FormFieldKey, _ as AnalyticsTrackEvent, a as FormsClient, b as FormsStorage, c as SubmitResult, d as FormsAnalyticsEvent, f as DataUpdatedEvent, g as AnalyticsInstance, h as FormSubmittedEvent, i as FormSchemaMap, j as SchemaError, k as JsonValue, l as ValidateResult, m as FieldUpdatedType, n as FieldErrors, o as InitFormsOptions, p as FieldUpdatedEvent, r as FormInstance, s as SetResult, t as CustomValidationsFor, u as initForms, v as AnalyticsTrackResult, w as FieldValidator, x as FieldConfig, y as FORM_DATA_KEY } from "./index-qfBhdk5L.js";
2
- export { type AnalyticsInstance, type AnalyticsTrackEvent, type AnalyticsTrackResult, type CustomValidationsFor, type DataUpdatedEvent, FORM_DATA_KEY, type FieldConfig, type FieldErrors, type FieldType, type FieldUpdatedEvent, type FieldUpdatedType, type FieldValidations, type FieldValidator, type FormFieldKey, type FormInstance, type FormSchema, type FormSchemaMap, type FormSubmittedEvent, type FormValues, type FormsAnalyticsEvent, type FormsClient, FormsError, type FormsStorage, type InitFormsOptions, type JsonValue, type ProtocolFieldId, SchemaError, type SetResult, type SubmitResult, type ValidateResult, ValidatorError, initForms };
1
+ import { a as FieldUpdatedEvent, c as FormsError, i as DataUpdatedEvent, l as SchemaError, n as FORM_DATA_KEY, o as FieldUpdatedType, r as FormsStorage, s as FormSubmittedEvent, t as FormsAnalyticsEvent, u as ValidatorError } from "./index-CmcgIxTH.js";
2
+ import { A as JsonValue, C as FormValues, D as AnalyticsInstance, E as FormFileRef, O as AnalyticsTrackEvent, S as FormSchema, T as SelectFieldOption, _ as FieldOption, a as FormSchemaMap, b as FieldValidator, c as FormsServerInitOptions, f as SetResult, g as FieldConfig, h as initForms, i as FormInstance, k as AnalyticsTrackResult, l as InitFormsOptions, m as ValidateResult, n as FieldErrors, o as FormServerDataEntry, p as SubmitResult, r as FormCustomValidationsEntry, s as FormsClient, t as CustomValidationsFor, v as FieldType, w as ProtocolFieldId, x as FormFieldKey, y as FieldValidations } from "./form-x4EJcR9A.js";
3
+ export { type AnalyticsInstance, type AnalyticsTrackEvent, type AnalyticsTrackResult, type CustomValidationsFor, type DataUpdatedEvent, FORM_DATA_KEY, type FieldConfig, type FieldErrors, type FieldOption, type FieldType, type FieldUpdatedEvent, type FieldUpdatedType, type FieldValidations, type FieldValidator, type FormCustomValidationsEntry, type FormFieldKey, type FormFileRef, type FormInstance, type FormSchema, type FormSchemaMap, type FormServerDataEntry, type FormSubmittedEvent, type FormValues, type FormsAnalyticsEvent, type FormsClient, FormsError, type FormsServerInitOptions, type FormsStorage, type InitFormsOptions, type JsonValue, type ProtocolFieldId, SchemaError, type SelectFieldOption, type SetResult, type SubmitResult, type ValidateResult, ValidatorError, initForms };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ValidatorError, i as SchemaError, n as FORM_DATA_KEY, r as FormsError, t as initForms } from "./form-9Tp91g6a.js";
1
+ import { a as FORM_DATA_KEY, c as SchemaError, l as ValidatorError, n as initForms, s as FormsError } from "./form-7wmC3G_q.js";
2
2
  export { FORM_DATA_KEY, FormsError, SchemaError, ValidatorError, initForms };
package/dist/react.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_form = require("./form-DzfCc5X2.cjs");
2
+ const require_form = require("./form-CdIfHqNc.cjs");
3
3
  let react = require("react");
4
4
  let _embeddables_core_react = require("@embeddables/core/react");
5
5
  //#region src/react/use-form-store.ts
@@ -66,14 +66,9 @@ function useRegisteredFormsClient() {
66
66
  * (`useForm<'signup', OtherSchemas>`) only to work against a map other than the
67
67
  * registered one.
68
68
  */
69
- function useForm({ formId, customValidations }) {
69
+ function useForm({ formId }) {
70
70
  const client = useRegisteredFormsClient();
71
- const customValidationsRef = (0, react.useRef)(customValidations);
72
- customValidationsRef.current = customValidations;
73
- const form = client === null ? null : client.getForm({
74
- formId,
75
- customValidations: customValidationsRef.current
76
- });
71
+ const form = client === null ? null : client.getForm({ formId });
77
72
  const { values, errors } = useFormSnapshot(form);
78
73
  return {
79
74
  form,
@@ -107,6 +102,11 @@ function useFormField({ form, key }) {
107
102
  const setValue = (0, react.useCallback)((value) => {
108
103
  setDraft(value);
109
104
  }, []);
105
+ const commit = (0, react.useCallback)((value) => {
106
+ if (form === null) return noopSetValue();
107
+ setDraft(value);
108
+ return form.set({ [key]: value });
109
+ }, [form, key]);
110
110
  const onBlur = (0, react.useCallback)(async () => {
111
111
  if (form === null) return noopSetValue();
112
112
  if (draft === committed) return {
@@ -124,16 +124,111 @@ function useFormField({ form, key }) {
124
124
  value: void 0,
125
125
  error: void 0,
126
126
  setValue: () => void 0,
127
+ commit: async () => noopSetValue(),
127
128
  onBlur: async () => noopSetValue()
128
129
  };
129
130
  return {
130
131
  value: draft,
131
132
  error: errors[key],
132
133
  setValue,
134
+ commit,
133
135
  onBlur
134
136
  };
135
137
  }
136
138
  //#endregion
139
+ //#region src/react/use-form-file-upload.ts
140
+ const noopSetResult = async () => ({
141
+ ok: false,
142
+ errors: {}
143
+ });
144
+ /**
145
+ * Reactive binding for a `type: file` field: uploads through the public API,
146
+ * commits the returned `FormFileRef`, and exposes a native `<input type="file">`
147
+ * handler.
148
+ */
149
+ function useFormFileUpload({ form, key }) {
150
+ const { values, errors } = useFormSnapshot(form);
151
+ const [isLoading, setIsLoading] = (0, react.useState)(false);
152
+ const [uploadError, setUploadError] = (0, react.useState)(void 0);
153
+ const uploadGenerationRef = (0, react.useRef)(0);
154
+ (0, react.useEffect)(() => {
155
+ return () => {
156
+ uploadGenerationRef.current += 1;
157
+ setIsLoading(false);
158
+ setUploadError(void 0);
159
+ };
160
+ }, [form, key]);
161
+ const upload = (0, react.useCallback)(async (file) => {
162
+ if (form === null) return noopSetResult();
163
+ const generation = ++uploadGenerationRef.current;
164
+ setIsLoading(true);
165
+ setUploadError(void 0);
166
+ try {
167
+ const ref = await form.uploadFile({
168
+ key,
169
+ file
170
+ });
171
+ if (generation !== uploadGenerationRef.current) return {
172
+ ok: false,
173
+ errors: {}
174
+ };
175
+ return await form.set({ [key]: ref });
176
+ } catch (error) {
177
+ if (generation !== uploadGenerationRef.current) return {
178
+ ok: false,
179
+ errors: {}
180
+ };
181
+ const message = error instanceof require_form.FormsError ? error.message : "File upload failed.";
182
+ setUploadError(message);
183
+ return {
184
+ ok: false,
185
+ errors: {}
186
+ };
187
+ } finally {
188
+ if (generation === uploadGenerationRef.current) setIsLoading(false);
189
+ }
190
+ }, [form, key]);
191
+ const clear = (0, react.useCallback)(async () => {
192
+ if (form === null) return noopSetResult();
193
+ uploadGenerationRef.current += 1;
194
+ setIsLoading(false);
195
+ setUploadError(void 0);
196
+ return form.set({ [key]: null });
197
+ }, [form, key]);
198
+ const onChange = (0, react.useCallback)((event) => {
199
+ const file = event.target.files?.[0];
200
+ if (file === void 0) return;
201
+ upload(file);
202
+ event.target.value = "";
203
+ }, [upload]);
204
+ if (form === null) return {
205
+ value: void 0,
206
+ error: void 0,
207
+ isLoading: false,
208
+ uploadError: void 0,
209
+ upload: async () => noopSetResult(),
210
+ clear: async () => noopSetResult(),
211
+ inputProps: {
212
+ type: "file",
213
+ disabled: true,
214
+ onChange: () => void 0
215
+ }
216
+ };
217
+ return {
218
+ value: values[key],
219
+ error: errors[key],
220
+ isLoading,
221
+ uploadError,
222
+ upload,
223
+ clear,
224
+ inputProps: {
225
+ type: "file",
226
+ disabled: isLoading,
227
+ onChange
228
+ }
229
+ };
230
+ }
231
+ //#endregion
137
232
  //#region src/react/resolve-core-analytics.ts
138
233
  function resolveCoreAnalyticsInstance(core) {
139
234
  if (typeof core.getAnalyticsInstance !== "function") return void 0;
@@ -141,10 +236,11 @@ function resolveCoreAnalyticsInstance(core) {
141
236
  }
142
237
  //#endregion
143
238
  //#region src/react/register-forms-client.ts
144
- function registerFormsClient({ core, ...options }) {
239
+ function register({ disableAnalyticsFallback, ...options }) {
240
+ const { core } = options;
145
241
  const existing = formsByCore.get(core);
146
242
  if (existing !== void 0) return existing;
147
- const analyticsInstance = options.analyticsInstance ?? resolveCoreAnalyticsInstance(core);
243
+ const analyticsInstance = disableAnalyticsFallback ? void 0 : options.analyticsInstance ?? resolveCoreAnalyticsInstance(core);
148
244
  const client = require_form.initForms({
149
245
  ...options,
150
246
  core,
@@ -153,15 +249,39 @@ function registerFormsClient({ core, ...options }) {
153
249
  formsByCore.set(core, client);
154
250
  return client;
155
251
  }
252
+ function registerFormsClient(options) {
253
+ return register(options);
254
+ }
255
+ function registerFormsModuleClient(options) {
256
+ return register(options);
257
+ }
156
258
  //#endregion
157
259
  //#region src/react/forms-module.ts
158
- function forms(options) {
260
+ const ANALYTICS_MODULE_KEY = "analytics";
261
+ function resolveInjectedAnalytics(value) {
262
+ if (value === void 0) return void 0;
263
+ if (typeof value !== "object" || value === null) throw new require_form.FormsError("Forms received an invalid Analytics module dependency.");
264
+ const candidate = value;
265
+ if (typeof candidate.trackEvent !== "function" || typeof candidate.getAppUserId !== "function" || typeof candidate.getProjectId !== "function") throw new require_form.FormsError("Forms received an invalid Analytics module dependency.");
266
+ return candidate;
267
+ }
268
+ function forms(options = {}) {
159
269
  return {
160
270
  key: FORMS_MODULE_KEY,
161
- init: (core) => registerFormsClient({
162
- core,
163
- ...options
164
- })
271
+ dependencies: [{
272
+ key: ANALYTICS_MODULE_KEY,
273
+ optional: true
274
+ }],
275
+ init: (core, context) => {
276
+ const analyticsInstance = context?.analyticsExcluded ? void 0 : options.analyticsInstance ?? resolveInjectedAnalytics(context?.dependencies.get(ANALYTICS_MODULE_KEY));
277
+ return registerFormsModuleClient({
278
+ core,
279
+ customValidations: context?.parameters?.customValidations ?? options.customValidations,
280
+ serverFormData: context?.parameters?.serverFormData ?? options.serverFormData,
281
+ analyticsInstance,
282
+ disableAnalyticsFallback: context?.analyticsExcluded
283
+ });
284
+ }
165
285
  };
166
286
  }
167
287
  //#endregion
@@ -171,5 +291,6 @@ exports.registerFormsClient = registerFormsClient;
171
291
  exports.useForm = useForm;
172
292
  exports.useFormErrors = useFormErrors;
173
293
  exports.useFormField = useFormField;
294
+ exports.useFormFileUpload = useFormFileUpload;
174
295
 
175
296
  //# sourceMappingURL=react.cjs.map