@conform-to/react 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,475 @@
1
1
  # @conform-to/react
2
2
 
3
- > View adapter for [react](https://github.com/facebook/react), built on top of `@conform-to/dom`
3
+ > [React](https://github.com/facebook/react) adapter for [conform](https://github.com/edmundhung/conform)
4
4
 
5
5
  ## API Reference
6
6
 
7
- - [conform](#conform)
8
- - [useControlledInput](#useControlledInput)
9
7
  - [useForm](#useForm)
10
8
  - [useFieldset](#useFieldset)
11
9
  - [useFieldList](#useFieldList)
10
+ - [useControlledInput](#useControlledInput)
11
+ - [conform](#conform)
12
12
 
13
- ### conform
14
-
15
- ### useControlledInput
13
+ ---
16
14
 
17
15
  ### useForm
18
16
 
17
+ By default, the browser calls [reportValidity()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reportValidity) on the form element when you submit the form. This checks the validity of all the fields in it and reports if there are errors through the bubbles.
18
+
19
+ This hook enhances this behaviour by allowing the developers to decide the best timing to start reporting errors using the `initialReport` option. This could start as earliest as the user typing or as late as the user submit the form.
20
+
21
+ But, setting `initialReport` to `onSubmit` still works different from the native browser behaviour, which basically calls `reportValidity()` only at the time a submit event is received. The `useForm` hook introduces a **touched** state to each fields. It will eagerly report the validity of the field once it is touched. Any errors reported later will be updated as soon as new errors are found.
22
+
23
+ Feel free to **SKIP** this if the native browser behaviour fullfills your need.
24
+
25
+ ```tsx
26
+ import { useForm } from '@conform-to/react';
27
+
28
+ function RandomForm() {
29
+ const formProps = useForm({
30
+ /**
31
+ * Decide when the error should be reported initially.
32
+ * The options are `onSubmit`, `onBlur` or `onChange`.
33
+ * Default to `onSubmit`
34
+ */
35
+ initialReport: 'onBlur',
36
+
37
+ /**
38
+ * Native browser report will be enabled before hydation
39
+ * if this is set to `true`. Default to `false`.
40
+ */
41
+ fallbackNative: true,
42
+
43
+ /**
44
+ * The form could be submitted regardless of the validity
45
+ * of the form if this is set to `true`. Default to
46
+ * `false`.
47
+ */
48
+ noValidate: false,
49
+
50
+ /**
51
+ * Form submit handler
52
+ *
53
+ * It will NOT be called if
54
+ * (1) one of the fields is invalid, and
55
+ * (2) noValidate is set to false
56
+ */
57
+ onSubmit(e) {
58
+ // ...
59
+ },
60
+
61
+ /**
62
+ * Form reset handler
63
+ */
64
+ onReset(e) {
65
+ // ...
66
+ },
67
+ });
68
+
69
+ return <form {...formProps}>{/* ... */}</form>;
70
+ }
71
+ ```
72
+
73
+ <details>
74
+ <summary>What is `formProps`?</summary>
75
+
76
+ It is a group of properties required to setup the form. They can also be set explicitly as shown below:
77
+
78
+ ```tsx
79
+ <form
80
+ ref={formProps.ref}
81
+ onSubmit={formProps.onSubmit}
82
+ onReset={formProps.onReset}
83
+ noValidate={formProps.noValidate}
84
+ >
85
+ {/* ... */}
86
+ </form>
87
+ ```
88
+
89
+ </details>
90
+
91
+ ---
92
+
19
93
  ### useFieldset
20
94
 
95
+ This hook prepares all the config you need to setup the fieldset based on the provided schema.
96
+
97
+ ```tsx
98
+ import { useFieldset } from '@conform-to/react';
99
+
100
+ /**
101
+ * Schema of the fieldset
102
+ *
103
+ * Defining a schema manually could be error-prone. It
104
+ * is strongly recommended to use a schema validation
105
+ * library with a schema resolver.
106
+ *
107
+ * Currently only Zod is supported and Yup support is
108
+ * coming soon. Please check the corresponding package
109
+ * for the setup required
110
+ */
111
+ const schema = /*
112
+ Assuming this to be a schema for book and it looks like this:
113
+
114
+ type Book = {
115
+ name: string;
116
+ isbn: string;
117
+ }
118
+
119
+ */
120
+
121
+ function BookFieldset() {
122
+ const [
123
+ fieldsetProps,
124
+ /**
125
+ * The variables `name` and `isbn` are FieldProps objects
126
+ * They are used to configure the field (input, select, textarea)
127
+ *
128
+ * Please check the docs of the `conform` helpers for how to
129
+ * use them together
130
+ */
131
+ {
132
+ name,
133
+ isbn,
134
+ },
135
+ ] = useFieldset(schema, {
136
+ /**
137
+ * Name of the fieldset
138
+ * Required only for nested fieldset.
139
+ */
140
+ name: 'book',
141
+
142
+ /**
143
+ * Id of the form
144
+ * Required only if the fieldset is placed out of the form
145
+ */
146
+ form: 'random-form-id',
147
+
148
+ /**
149
+ * Default value of the fieldset
150
+ */
151
+ defaultValue: {
152
+ isbn: '0340013818',
153
+ },
154
+
155
+ /**
156
+ * Error reported by the server
157
+ */
158
+ error: {
159
+ isbn: 'Invalid ISBN',
160
+ },
161
+ });
162
+
163
+ const {
164
+ /**
165
+ * This would be `book.isbn` instead of `isbn`
166
+ * if the `name` option is provided
167
+ */
168
+ name,
169
+
170
+ /**
171
+ * This would be `random-form-id`
172
+ * because of the `form` option provided
173
+ */
174
+ form,
175
+
176
+ /**
177
+ * This would be `0340013818` if specified
178
+ * on the `initalValue` option
179
+ */
180
+ defaultValue,
181
+
182
+ /**
183
+ * Current error message
184
+ * This would be 'Invalid ISBN' initially if specified
185
+ * on the `error` option
186
+ */
187
+ error,
188
+
189
+ /**
190
+ * Constraint of the field (required, minLength etc)
191
+ *
192
+ * For example, the constraint of the isbn field could be:
193
+ * {
194
+ * required: true,
195
+ * pattern: '[0-9]{10,13}'
196
+ * }
197
+ */
198
+ ...constraint,
199
+ } = isbn;
200
+
201
+ return (
202
+ <fieldset {...fieldsetProps}>
203
+ {/* ... */}
204
+ </fieldset>)
205
+ );
206
+ }
207
+ ```
208
+
209
+ <details>
210
+ <summary>What is `fieldsetProps`?</summary>
211
+
212
+ It is a group of properties required to setup the fieldset. They can also be set explicitly as shown below:
213
+
214
+ ```tsx
215
+ <fieldset
216
+ ref={fieldsetProps.ref}
217
+ name={fieldsetProps.name}
218
+ form={fieldsetProps.form}
219
+ onInput={fieldsetProps.onInput}
220
+ onInvalid={fieldsetProps.onInvalid}
221
+ >
222
+ {/* ... */}
223
+ </fieldset>
224
+ ```
225
+
226
+ </details>
227
+
228
+ <details>
229
+ <summary>How is a schema looks like?</summary>
230
+
231
+ ```tsx
232
+ import type { Schema } from '@conform-to/react';
233
+
234
+ /**
235
+ * Defining a schema manually
236
+ */
237
+ const bookSchema: Schema<{
238
+ name: string;
239
+ isbn: string;
240
+ quantity?: number;
241
+ }> = {
242
+ /**
243
+ * Define the fields with its constraint together
244
+ */
245
+ fields: {
246
+ name: {
247
+ required: true,
248
+ },
249
+ isbn: {
250
+ required: true,
251
+ minLength: 10,
252
+ maxLength: 13,
253
+ pattern: '[0-9]{10,13}',
254
+ },
255
+ quantity: {
256
+ min: '0',
257
+ },
258
+ },
259
+
260
+ /**
261
+ * Customise validation behaviour
262
+ * Fallbacks to native browser validation if not specified
263
+ */
264
+ validate(fieldset) {
265
+ /**
266
+ * Lookup the field elements using the fieldset element
267
+ */
268
+ const [name] = getFieldElements(fieldset, 'name');
269
+
270
+ if (name.validity.valueMissing) {
271
+ /**
272
+ * Setting error message based on validity
273
+ */
274
+ name.setCustomValidity('Required');
275
+ } else if (name.value === 'something') {
276
+ /**
277
+ * Setting error message based on custom constraint
278
+ */
279
+ name.setCustomValidity('Please enter a valid name');
280
+ } else {
281
+ /**
282
+ * Clearing the error message (Important!)
283
+ */
284
+ name.setCustomValidity('');
285
+ }
286
+ },
287
+ };
288
+ ```
289
+
290
+ </details>
291
+
292
+ ---
293
+
21
294
  ### useFieldList
295
+
296
+ This hook is used in combination with `useFieldset` to handle array structure:
297
+
298
+ ```tsx
299
+ import { useFieldset, useFieldList } from '@conform-to/react';
300
+
301
+ /**
302
+ * Consider the schema as follow:
303
+ *
304
+ * type Collection = {
305
+ * books: Array<{ name: string; isbn: string; }>
306
+ * }
307
+ */
308
+
309
+ function CollectionForm() {
310
+ const [fieldsetProps, { books }] = useFieldset(collectionSchema);
311
+ const [bookList, control] = useFieldList(books);
312
+
313
+ return (
314
+ <fieldset {...fieldsetProps}>
315
+ {bookList.map((book, index) => (
316
+ <div key={book.key}>
317
+ {/* `book.props` is a FieldProps object similar to `books` */}
318
+ <BookFieldset {...book.props}>
319
+
320
+ {/* To setup a delete button */}
321
+ <button {...control.remove(index)}>Delete</button>
322
+ </div>
323
+ ))}
324
+
325
+ {/* To setup a button that can append a new row */}
326
+ <button {...control.append()}>add</button>
327
+ </fieldset>
328
+ );
329
+ }
330
+
331
+ /**
332
+ * This is basically the BookFieldset component from
333
+ * the `useFieldset` example, but setting all the
334
+ * options with the component props instead
335
+ */
336
+ function BookFieldset({ name, form, defaultValue, error }) {
337
+ const [fieldsetProps, { name, isbn }] = useFieldset(bookSchema, {
338
+ name,
339
+ form,
340
+ defaultValue,
341
+ error,
342
+ });
343
+
344
+ return (
345
+ <fieldset {...fieldsetProps}>
346
+ {/* ... */}
347
+ </fieldset>
348
+ );
349
+ }
350
+ ```
351
+
352
+ <details>
353
+ <summary>What can I do with `controls`?</summary>
354
+
355
+ ```tsx
356
+ // To append a new row with optional defaultValue
357
+ <button {...controls.append(defaultValue)}>Append</button>;
358
+
359
+ // To prepend a new row with optional defaultValue
360
+ <button {...controls.prepend(defaultValue)}>Prepend</button>;
361
+
362
+ // To remove a row by index
363
+ <button {...controls.remove(index)}>Remove</button>;
364
+
365
+ // To replace a row with another defaultValue
366
+ <button {...controls.replace(index, defaultValue)}>Replace</button>;
367
+
368
+ // To reorder a particular row to an another index
369
+ <button {...controls.reorder(fromIndex, toIndex)}>Reorder</button>;
370
+ ```
371
+
372
+ </details>
373
+
374
+ ---
375
+
376
+ ### useControlledInput
377
+
378
+ This hooks creates a shadow input that would be used to validate against the schema. Mainly used to get around problem integrating with controlled component.
379
+
380
+ ```tsx
381
+ import { useControlledInput } from '@conform-to/react';
382
+ import { Select, MenuItem } from '@mui/material';
383
+
384
+ function RandomFieldset() {
385
+ const [fieldsetProps, { category }] = useFieldset(schema);
386
+ const [input, control] = useControlledInput(category);
387
+
388
+ return (
389
+ <fieldset {...fieldsetProps}>
390
+ {/* Render the shadow input somewhere within the fieldset */}
391
+ {input}
392
+
393
+ {/* MUI Select is a controlled component */}
394
+ <Select
395
+ label="Category"
396
+ value={control.value ?? ''}
397
+ onChange={(e) => control.onChange(e.target.value)}
398
+ onBlur={() => control.onBlur()}
399
+ error={Boolean(category.error)}
400
+ helperText={category.error}
401
+ >
402
+ <MenuItem value="">Please select</MenuItem>
403
+ <MenuItem value="a">Category A</MenuItem>
404
+ <MenuItem value="b">Category B</MenuItem>
405
+ <MenuItem value="c">Category C</MenuItem>
406
+ </TextField>
407
+ </fieldset>
408
+ )
409
+ }
410
+ ```
411
+
412
+ ---
413
+
414
+ ### conform
415
+
416
+ It provides several helpers to setup a native input field quickly:
417
+
418
+ ```tsx
419
+ import { conform } from '@conform-to/react';
420
+
421
+ function RandomForm() {
422
+ const [setupFieldset, { cateogry }] = useFieldset(/* ... */);
423
+
424
+ return (
425
+ <fieldset {...setupFieldset}>
426
+ <input {...conform.input(cateogry, { type: 'text' })} />
427
+ <textarea {...conform.textarea(cateogry)} />
428
+ <select {...conform.select(cateogry)}>{/* ... */}</select>
429
+ </fieldset>
430
+ );
431
+ }
432
+ ```
433
+
434
+ This is equivalent to:
435
+
436
+ ```tsx
437
+ function RandomForm() {
438
+ const [setupFieldset, { cateogry }] = useFieldset(/* ... */);
439
+
440
+ return (
441
+ <fieldset {...setupFieldset}>
442
+ <input
443
+ type="text"
444
+ name={cateogry.name}
445
+ form={cateogry.form}
446
+ defaultValue={cateogry.defaultValue}
447
+ requried={cateogry.required}
448
+ minLength={cateogry.minLength}
449
+ maxLength={cateogry.maxLength}
450
+ min={cateogry.min}
451
+ max={cateogry.max}
452
+ multiple={cateogry.multiple}
453
+ pattern={cateogry.pattern}
454
+ >
455
+ <textarea
456
+ name={cateogry.name}
457
+ form={cateogry.form}
458
+ defaultValue={cateogry.defaultValue}
459
+ requried={cateogry.required}
460
+ minLength={cateogry.minLength}
461
+ maxLength={cateogry.maxLength}
462
+ />
463
+ <select
464
+ name={cateogry.name}
465
+ form={cateogry.form}
466
+ defaultValue={cateogry.defaultValue}
467
+ requried={cateogry.required}
468
+ multiple={cateogry.multiple}
469
+ >
470
+ {/* ... */}
471
+ </select>
472
+ </fieldset>
473
+ );
474
+ }
475
+ ```
package/helpers.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { type FieldConfig } from '@conform-to/dom';
1
+ import { type FieldProps } from '@conform-to/dom';
2
2
  import { type InputHTMLAttributes, type SelectHTMLAttributes, type TextareaHTMLAttributes } from 'react';
3
- export declare function input<Type extends string | number | Date | boolean | undefined>(config: FieldConfig<Type>, { type, value }?: {
3
+ export declare function input<Type extends string | number | Date | boolean | undefined>(props: FieldProps<Type>, { type, value }?: {
4
4
  type?: string;
5
5
  value?: string;
6
6
  }): InputHTMLAttributes<HTMLInputElement>;
7
- export declare function select<T extends any>(config: FieldConfig<T>): SelectHTMLAttributes<HTMLSelectElement>;
8
- export declare function textarea<T extends string | undefined>(config: FieldConfig<T>): TextareaHTMLAttributes<HTMLTextAreaElement>;
7
+ export declare function select<T extends any>(props: FieldProps<T>): SelectHTMLAttributes<HTMLSelectElement>;
8
+ export declare function textarea<T extends string | undefined>(props: FieldProps<T>): TextareaHTMLAttributes<HTMLTextAreaElement>;
package/helpers.js CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- function input(config) {
6
- var _config$constraint, _config$constraint2, _config$constraint3, _config$constraint4, _config$constraint5, _config$constraint6, _config$constraint7;
7
-
5
+ function input(props) {
8
6
  var {
9
7
  type,
10
8
  value
@@ -12,49 +10,50 @@ function input(config) {
12
10
  var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
13
11
  var attributes = {
14
12
  type,
15
- name: config.name,
16
- form: config.form,
17
- required: (_config$constraint = config.constraint) === null || _config$constraint === void 0 ? void 0 : _config$constraint.required,
18
- minLength: (_config$constraint2 = config.constraint) === null || _config$constraint2 === void 0 ? void 0 : _config$constraint2.minLength,
19
- maxLength: (_config$constraint3 = config.constraint) === null || _config$constraint3 === void 0 ? void 0 : _config$constraint3.maxLength,
20
- min: (_config$constraint4 = config.constraint) === null || _config$constraint4 === void 0 ? void 0 : _config$constraint4.min,
21
- max: (_config$constraint5 = config.constraint) === null || _config$constraint5 === void 0 ? void 0 : _config$constraint5.max,
22
- step: (_config$constraint6 = config.constraint) === null || _config$constraint6 === void 0 ? void 0 : _config$constraint6.step,
23
- pattern: (_config$constraint7 = config.constraint) === null || _config$constraint7 === void 0 ? void 0 : _config$constraint7.pattern
13
+ name: props.name,
14
+ form: props.form,
15
+ required: props.required,
16
+ minLength: props.minLength,
17
+ maxLength: props.maxLength,
18
+ min: props.min,
19
+ max: props.max,
20
+ step: props.step,
21
+ pattern: props.pattern,
22
+ multiple: props.multiple
24
23
  };
25
24
 
26
25
  if (isCheckboxOrRadio) {
27
26
  attributes.value = value !== null && value !== void 0 ? value : 'on';
28
- attributes.defaultChecked = config.initialValue === attributes.value;
27
+ attributes.defaultChecked = props.defaultValue === attributes.value;
29
28
  } else {
30
- var _config$initialValue;
29
+ var _props$defaultValue;
31
30
 
32
- attributes.defaultValue = "".concat((_config$initialValue = config.initialValue) !== null && _config$initialValue !== void 0 ? _config$initialValue : '');
31
+ attributes.defaultValue = "".concat((_props$defaultValue = props.defaultValue) !== null && _props$defaultValue !== void 0 ? _props$defaultValue : '');
33
32
  }
34
33
 
35
34
  return attributes;
36
35
  }
37
- function select(config) {
38
- var _config$initialValue2, _config$constraint8, _config$constraint9;
36
+ function select(props) {
37
+ var _props$defaultValue2;
39
38
 
40
39
  return {
41
- name: config.name,
42
- form: config.form,
43
- defaultValue: "".concat((_config$initialValue2 = config.initialValue) !== null && _config$initialValue2 !== void 0 ? _config$initialValue2 : ''),
44
- required: (_config$constraint8 = config.constraint) === null || _config$constraint8 === void 0 ? void 0 : _config$constraint8.required,
45
- multiple: (_config$constraint9 = config.constraint) === null || _config$constraint9 === void 0 ? void 0 : _config$constraint9.multiple
40
+ name: props.name,
41
+ form: props.form,
42
+ defaultValue: props.multiple ? Array.isArray(props.defaultValue) ? props.defaultValue : [] : "".concat((_props$defaultValue2 = props.defaultValue) !== null && _props$defaultValue2 !== void 0 ? _props$defaultValue2 : ''),
43
+ required: props.required,
44
+ multiple: props.multiple
46
45
  };
47
46
  }
48
- function textarea(config) {
49
- var _config$initialValue3, _config$constraint10, _config$constraint11, _config$constraint12;
47
+ function textarea(props) {
48
+ var _props$defaultValue3;
50
49
 
51
50
  return {
52
- name: config.name,
53
- form: config.form,
54
- defaultValue: "".concat((_config$initialValue3 = config.initialValue) !== null && _config$initialValue3 !== void 0 ? _config$initialValue3 : ''),
55
- required: (_config$constraint10 = config.constraint) === null || _config$constraint10 === void 0 ? void 0 : _config$constraint10.required,
56
- minLength: (_config$constraint11 = config.constraint) === null || _config$constraint11 === void 0 ? void 0 : _config$constraint11.minLength,
57
- maxLength: (_config$constraint12 = config.constraint) === null || _config$constraint12 === void 0 ? void 0 : _config$constraint12.maxLength
51
+ name: props.name,
52
+ form: props.form,
53
+ defaultValue: "".concat((_props$defaultValue3 = props.defaultValue) !== null && _props$defaultValue3 !== void 0 ? _props$defaultValue3 : ''),
54
+ required: props.required,
55
+ minLength: props.minLength,
56
+ maxLength: props.maxLength
58
57
  };
59
58
  }
60
59
 
package/hooks.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { type FieldConfig, type Schema } from '@conform-to/dom';
1
+ import { type FieldProps, type Schema, type FieldsetData } from '@conform-to/dom';
2
2
  import { type ButtonHTMLAttributes, type FormEventHandler, type FormHTMLAttributes, type RefObject, type ReactElement } from 'react';
3
- interface FormConfig {
3
+ export interface FormConfig {
4
4
  /**
5
5
  * Decide when the error should be reported initially.
6
6
  * Default to `onSubmit`
@@ -30,35 +30,35 @@ interface FormProps {
30
30
  noValidate: Required<FormHTMLAttributes<HTMLFormElement>>['noValidate'];
31
31
  }
32
32
  export declare function useForm({ onReset, onSubmit, noValidate, fallbackNative, initialReport, }?: FormConfig): FormProps;
33
- interface FieldsetConfig<Type> extends Partial<FieldConfig<Type>> {
34
- }
33
+ export declare type FieldsetConfig<Type> = Partial<Pick<FieldProps<Type>, 'name' | 'form' | 'defaultValue' | 'error'>>;
35
34
  interface FieldsetProps {
36
35
  ref: RefObject<HTMLFieldSetElement>;
37
36
  name?: string;
38
37
  form?: string;
39
38
  onInput: FormEventHandler<HTMLFieldSetElement>;
40
- onReset: FormEventHandler<HTMLFieldSetElement>;
41
39
  onInvalid: FormEventHandler<HTMLFieldSetElement>;
42
40
  }
43
41
  export declare function useFieldset<Type extends Record<string, any>>(schema: Schema<Type>, config?: FieldsetConfig<Type>): [FieldsetProps, {
44
- [Key in keyof Type]-?: FieldConfig<Type[Key]>;
42
+ [Key in keyof Type]-?: FieldProps<Type[Key]>;
45
43
  }];
46
- interface FieldListControl {
47
- prepend(): ButtonHTMLAttributes<HTMLButtonElement>;
48
- append(): ButtonHTMLAttributes<HTMLButtonElement>;
44
+ interface FieldListControl<T> {
45
+ prepend(defaultValue?: FieldsetData<T, string>): ButtonHTMLAttributes<HTMLButtonElement>;
46
+ append(defaultValue?: FieldsetData<T, string>): ButtonHTMLAttributes<HTMLButtonElement>;
47
+ replace(index: number, defaultValue: FieldsetData<T, string>): ButtonHTMLAttributes<HTMLButtonElement>;
49
48
  remove(index: number): ButtonHTMLAttributes<HTMLButtonElement>;
49
+ reorder(fromIndex: number, toIndex: number): ButtonHTMLAttributes<HTMLButtonElement>;
50
50
  }
51
- export declare function useFieldList<Type extends Array<any>>(config: FieldConfig<Type>): [
51
+ export declare function useFieldList<Payload>(props: FieldProps<Array<Payload>>): [
52
52
  Array<{
53
53
  key: string;
54
- config: FieldConfig<Type extends Array<infer InnerType> ? InnerType : never>;
54
+ props: FieldProps<Payload>;
55
55
  }>,
56
- FieldListControl
56
+ FieldListControl<Payload>
57
57
  ];
58
58
  interface InputControl {
59
59
  value: string;
60
60
  onChange: (value: string) => void;
61
61
  onBlur: () => void;
62
62
  }
63
- export declare function useControlledInput<T extends string | number | Date | undefined>(field: FieldConfig<T>): [ReactElement, InputControl];
63
+ export declare function useControlledInput<T extends string | number | Date | undefined>(field: FieldProps<T>): [ReactElement, InputControl];
64
64
  export {};