@dxos/react-ui-form 0.7.5-labs.e27f9b9 → 0.7.5-main.2567c87
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/dist/lib/browser/index.mjs +467 -726
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +506 -750
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +467 -726
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts +6 -5
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +21 -12
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +1 -2
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +6 -5
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +2 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +2 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/factory.d.ts +2 -2
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +0 -5
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +11 -10
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +0 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +27 -28
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.tsx +6 -7
- package/src/components/Form/Defaults.tsx +33 -20
- package/src/components/Form/Form.stories.tsx +5 -15
- package/src/components/Form/Form.tsx +355 -46
- package/src/components/Form/Input.tsx +6 -5
- package/src/components/Form/custom/GeoPointInput.tsx +15 -6
- package/src/components/Form/custom/TupleInput.tsx +9 -5
- package/src/components/Form/custom/index.ts +0 -1
- package/src/components/Form/factory.tsx +5 -2
- package/src/components/ViewEditor/ViewEditor.tsx +2 -5
- package/src/components/deprecated/FormInput.tsx +7 -20
- package/src/hooks/useForm.ts +49 -57
- package/src/translations.ts +0 -8
- package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
- package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -7
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -26
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -20
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
- package/src/components/Form/ArrayField.tsx +0 -111
- package/src/components/Form/FormActions.tsx +0 -41
- package/src/components/Form/FormContent.tsx +0 -173
- package/src/components/Form/FormContext.tsx +0 -48
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -58
- package/src/components/Form/custom/SelectOptionsInput.tsx +0 -192
|
@@ -4,29 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type FC, type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type FormatEnum } from '@dxos/echo-schema';
|
|
7
|
+
import { type BaseObject, type FormatEnum, type PropertyKey } from '@dxos/echo-schema';
|
|
8
8
|
import { type SimpleType } from '@dxos/effect';
|
|
9
9
|
import { Icon, Tooltip } from '@dxos/react-ui';
|
|
10
10
|
import { errorText } from '@dxos/react-ui-theme';
|
|
11
11
|
|
|
12
|
-
import { type
|
|
12
|
+
import { type FormHandler } from '../../hooks';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Props passed to input components.
|
|
16
16
|
*/
|
|
17
|
-
export type InputProps = {
|
|
17
|
+
export type InputProps<T extends BaseObject> = {
|
|
18
|
+
property: PropertyKey<T>;
|
|
18
19
|
type: SimpleType;
|
|
19
20
|
format?: FormatEnum;
|
|
20
21
|
label?: string;
|
|
21
22
|
disabled?: boolean;
|
|
22
23
|
placeholder?: string;
|
|
23
24
|
inputOnly?: boolean;
|
|
24
|
-
} &
|
|
25
|
+
} & Pick<FormHandler<T>, 'getStatus' | 'getValue' | 'onValueChange' | 'onBlur'>;
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Form input component.
|
|
28
29
|
*/
|
|
29
|
-
export type InputComponent = FC<InputProps
|
|
30
|
+
export type InputComponent<T extends BaseObject> = FC<InputProps<T>>;
|
|
30
31
|
|
|
31
32
|
export type InputHeaderProps = PropsWithChildren<{
|
|
32
33
|
error?: string;
|
|
@@ -4,17 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { type GeoPoint } from '@dxos/echo-schema';
|
|
7
|
+
import { type BaseObject, type GeoPoint } from '@dxos/echo-schema';
|
|
8
8
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { safeParseFloat } from '@dxos/util';
|
|
10
10
|
|
|
11
11
|
import { translationKey } from '../../../translations';
|
|
12
12
|
import { InputHeader, type InputProps } from '../Input';
|
|
13
13
|
|
|
14
|
-
export const GeoPointInput =
|
|
14
|
+
export const GeoPointInput = <T extends BaseObject>({
|
|
15
|
+
property,
|
|
16
|
+
type,
|
|
17
|
+
label,
|
|
18
|
+
disabled,
|
|
19
|
+
getStatus,
|
|
20
|
+
getValue,
|
|
21
|
+
onValueChange,
|
|
22
|
+
onBlur,
|
|
23
|
+
}: InputProps<T>) => {
|
|
15
24
|
const { t } = useTranslation(translationKey);
|
|
16
|
-
const { status, error } = getStatus();
|
|
17
|
-
const [lng = 0, lat = 0] = getValue<GeoPoint>() ?? [];
|
|
25
|
+
const { status, error } = getStatus?.(property);
|
|
26
|
+
const [lng = 0, lat = 0] = getValue<GeoPoint>(property) ?? [];
|
|
18
27
|
|
|
19
28
|
return (
|
|
20
29
|
<Input.Root validationValence={status}>
|
|
@@ -27,7 +36,7 @@ export const GeoPointInput = ({ type, label, disabled, getStatus, getValue, onVa
|
|
|
27
36
|
disabled={disabled}
|
|
28
37
|
placeholder={t('placeholder longitude')}
|
|
29
38
|
value={lng}
|
|
30
|
-
onChange={(event) => onValueChange(type, [lat, safeParseFloat(event.target.value, 0)])}
|
|
39
|
+
onChange={(event) => onValueChange(property, type, [lat, safeParseFloat(event.target.value, 0)])}
|
|
31
40
|
onBlur={onBlur}
|
|
32
41
|
/>
|
|
33
42
|
<Input.TextInput
|
|
@@ -35,7 +44,7 @@ export const GeoPointInput = ({ type, label, disabled, getStatus, getValue, onVa
|
|
|
35
44
|
disabled={disabled}
|
|
36
45
|
placeholder={t('placeholder latitude')}
|
|
37
46
|
value={lat}
|
|
38
|
-
onChange={(event) => onValueChange(type, [safeParseFloat(event.target.value, 0), lng])}
|
|
47
|
+
onChange={(event) => onValueChange(property, type, [safeParseFloat(event.target.value, 0), lng])}
|
|
39
48
|
onBlur={onBlur}
|
|
40
49
|
/>
|
|
41
50
|
</div>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
+
import { type BaseObject } from '@dxos/echo-schema';
|
|
7
8
|
import { Input } from '@dxos/react-ui';
|
|
8
9
|
import { mx } from '@dxos/react-ui-theme';
|
|
9
10
|
import { safeParseFloat } from '@dxos/util';
|
|
@@ -12,8 +13,9 @@ import { InputHeader, type InputProps } from '../Input';
|
|
|
12
13
|
|
|
13
14
|
const gridCols = ['grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4'];
|
|
14
15
|
|
|
15
|
-
export const TupleInput = ({
|
|
16
|
+
export const TupleInput = <T extends BaseObject>({
|
|
16
17
|
binding,
|
|
18
|
+
property,
|
|
17
19
|
type,
|
|
18
20
|
label,
|
|
19
21
|
disabled,
|
|
@@ -21,11 +23,11 @@ export const TupleInput = ({
|
|
|
21
23
|
getValue,
|
|
22
24
|
onValueChange,
|
|
23
25
|
onBlur,
|
|
24
|
-
}: InputProps & { binding: string[] }) => {
|
|
25
|
-
const { status, error } = getStatus();
|
|
26
|
+
}: InputProps<T> & { binding: string[] }) => {
|
|
27
|
+
const { status, error } = getStatus?.(property);
|
|
26
28
|
|
|
27
29
|
// TODO(burdon): Generalize number/float/string, etc.
|
|
28
|
-
const values: Record<string, number> = getValue<Record<string, number>>() ?? {};
|
|
30
|
+
const values: Record<string, number> = getValue<Record<string, number>>(property) ?? {};
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<Input.Root validationValence={status}>
|
|
@@ -39,7 +41,9 @@ export const TupleInput = ({
|
|
|
39
41
|
type='number'
|
|
40
42
|
disabled={disabled}
|
|
41
43
|
value={values[prop]}
|
|
42
|
-
onChange={(event) =>
|
|
44
|
+
onChange={(event) =>
|
|
45
|
+
onValueChange(property, type, { ...values, [prop]: safeParseFloat(event.target.value, 0) })
|
|
46
|
+
}
|
|
43
47
|
onBlur={onBlur}
|
|
44
48
|
/>
|
|
45
49
|
))}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { FormatEnum } from '@dxos/echo-schema';
|
|
5
|
+
import { type BaseObject, FormatEnum } from '@dxos/echo-schema';
|
|
6
6
|
import { type SimpleType } from '@dxos/effect';
|
|
7
7
|
|
|
8
8
|
import { BooleanInput, NumberInput, TextInput } from './Defaults';
|
|
@@ -12,7 +12,10 @@ import { GeoPointInput } from './custom';
|
|
|
12
12
|
/**
|
|
13
13
|
* Get property input component.
|
|
14
14
|
*/
|
|
15
|
-
export const getInputComponent =
|
|
15
|
+
export const getInputComponent = <T extends BaseObject>(
|
|
16
|
+
type: SimpleType,
|
|
17
|
+
format?: FormatEnum,
|
|
18
|
+
): InputComponent<T> | undefined => {
|
|
16
19
|
switch (format) {
|
|
17
20
|
case FormatEnum.LatLng:
|
|
18
21
|
return GeoPointInput;
|
|
@@ -10,6 +10,7 @@ import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui
|
|
|
10
10
|
import { List } from '@dxos/react-ui-list';
|
|
11
11
|
import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
|
|
12
12
|
import { FieldSchema, type FieldType, type ViewType, ViewProjection, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
13
|
+
import { arrayMove } from '@dxos/util';
|
|
13
14
|
|
|
14
15
|
import { translationKey } from '../../translations';
|
|
15
16
|
import { FieldEditor } from '../FieldEditor';
|
|
@@ -90,11 +91,7 @@ export const ViewEditor = ({
|
|
|
90
91
|
|
|
91
92
|
const handleMove = useCallback(
|
|
92
93
|
(fromIndex: number, toIndex: number) => {
|
|
93
|
-
|
|
94
|
-
const fields = [...view.fields];
|
|
95
|
-
const [moved] = fields.splice(fromIndex, 1);
|
|
96
|
-
fields.splice(toIndex, 0, moved);
|
|
97
|
-
view.fields = fields;
|
|
94
|
+
arrayMove(view.fields, fromIndex, toIndex);
|
|
98
95
|
},
|
|
99
96
|
[view.fields],
|
|
100
97
|
);
|
|
@@ -4,21 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type JSX, type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Input
|
|
8
|
-
import { mx, textBlockWidth } from '@dxos/react-ui-theme';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @deprecated
|
|
12
|
-
*/
|
|
13
|
-
export const DeprecatedFormContainer = ({ children, classNames }: ThemedClassName<PropsWithChildren>) => {
|
|
14
|
-
return (
|
|
15
|
-
<div role='none' className='w-full p-4 justify-center overflow-x-hidden overflow-y-auto'>
|
|
16
|
-
<div role='form' className={mx('flex flex-col', textBlockWidth, classNames)}>
|
|
17
|
-
{children}
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
7
|
+
import { Input } from '@dxos/react-ui';
|
|
22
8
|
|
|
23
9
|
export type DeprecatedFormInputProps = {
|
|
24
10
|
label: string;
|
|
@@ -29,6 +15,7 @@ export type DeprecatedFormInputProps = {
|
|
|
29
15
|
/**
|
|
30
16
|
* @deprecated
|
|
31
17
|
*/
|
|
18
|
+
// TODO(burdon): Still used in ObservabilitySettings and all settings.
|
|
32
19
|
export const DeprecatedFormInput = ({
|
|
33
20
|
label,
|
|
34
21
|
description,
|
|
@@ -39,9 +26,8 @@ export const DeprecatedFormInput = ({
|
|
|
39
26
|
<div role='none' className='flex w-full gap-4 py-1'>
|
|
40
27
|
<Input.Root>
|
|
41
28
|
<div role='none' className='flex flex-col w-full'>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</Input.Label>
|
|
29
|
+
{/* TODO(burdon): Consistent height for controls (e.g., Select, Textbox, and Checkbox are all different). */}
|
|
30
|
+
<Input.Label classNames='flex min-h-[40px] items-center'>{label}</Input.Label>
|
|
45
31
|
{description && (
|
|
46
32
|
<Input.DescriptionAndValidation classNames='mbs-0.5'>
|
|
47
33
|
<Input.Description>{description}</Input.Description>
|
|
@@ -50,7 +36,7 @@ export const DeprecatedFormInput = ({
|
|
|
50
36
|
</div>
|
|
51
37
|
|
|
52
38
|
<div role='none'>
|
|
53
|
-
<div role='none' className='flex min-
|
|
39
|
+
<div role='none' className='flex min-h-[40px] items-center'>
|
|
54
40
|
{children}
|
|
55
41
|
</div>
|
|
56
42
|
</div>
|
|
@@ -59,8 +45,9 @@ export const DeprecatedFormInput = ({
|
|
|
59
45
|
);
|
|
60
46
|
|
|
61
47
|
if (secondary) {
|
|
48
|
+
// console.log(secondary);
|
|
62
49
|
return (
|
|
63
|
-
<div role='none'>
|
|
50
|
+
<div role='none' className='flex flex-col w-full'>
|
|
64
51
|
{primary}
|
|
65
52
|
{secondary}
|
|
66
53
|
</div>
|
package/src/hooks/useForm.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { type FocusEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
|
|
8
|
-
import {
|
|
7
|
+
import { type BaseObject, type PropertyKey, getValue, setValue } from '@dxos/echo-schema';
|
|
8
|
+
import { type SimpleType, type S, type JsonPath } from '@dxos/effect';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { validateSchema, type ValidationError } from '@dxos/schema';
|
|
@@ -20,9 +20,9 @@ export type FormHandler<T extends BaseObject> = {
|
|
|
20
20
|
//
|
|
21
21
|
|
|
22
22
|
values: Partial<T>;
|
|
23
|
-
errors: Record<
|
|
24
|
-
touched: Record<
|
|
25
|
-
changed: Record<
|
|
23
|
+
errors: Record<PropertyKey<T>, string>;
|
|
24
|
+
touched: Record<PropertyKey<T>, boolean>;
|
|
25
|
+
changed: Record<PropertyKey<T>, boolean>;
|
|
26
26
|
canSave: boolean;
|
|
27
27
|
handleSave: () => void;
|
|
28
28
|
|
|
@@ -30,10 +30,10 @@ export type FormHandler<T extends BaseObject> = {
|
|
|
30
30
|
// Form input component helpers.
|
|
31
31
|
//
|
|
32
32
|
|
|
33
|
-
getStatus: (
|
|
34
|
-
getValue: <V>(
|
|
35
|
-
onValueChange: <V>(
|
|
36
|
-
|
|
33
|
+
getStatus: (property: PropertyKey<T>) => { status?: 'error'; error?: string };
|
|
34
|
+
getValue: <V>(property: PropertyKey<T>) => V | undefined;
|
|
35
|
+
onValueChange: <V>(property: PropertyKey<T>, type: SimpleType, value: V) => void;
|
|
36
|
+
onBlur: (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -41,8 +41,9 @@ export type FormHandler<T extends BaseObject> = {
|
|
|
41
41
|
*/
|
|
42
42
|
export interface FormOptions<T extends BaseObject> {
|
|
43
43
|
/**
|
|
44
|
-
* Effect schema
|
|
44
|
+
* Effect schema.
|
|
45
45
|
*/
|
|
46
|
+
// TODO(burdon): Change to S.Struct<T>?
|
|
46
47
|
schema: S.Schema<T>;
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -88,22 +89,21 @@ export const useForm = <T extends BaseObject>({
|
|
|
88
89
|
onValid,
|
|
89
90
|
onSave,
|
|
90
91
|
}: FormOptions<T>): FormHandler<T> => {
|
|
91
|
-
invariant(AST.isTypeLiteral(schema.ast));
|
|
92
|
-
|
|
93
92
|
const [values, setValues] = useState<Partial<T>>(initialValues);
|
|
94
93
|
useEffect(() => {
|
|
95
94
|
setValues(initialValues);
|
|
96
95
|
}, [initialValues]);
|
|
97
96
|
|
|
98
|
-
const [touched, setTouched] = useState<Record<
|
|
99
|
-
const [changed, setChanged] = useState<Record<
|
|
100
|
-
const [errors, setErrors] = useState<Record<
|
|
97
|
+
const [touched, setTouched] = useState<Record<PropertyKey<T>, boolean>>(createKeySet(initialValues, false));
|
|
98
|
+
const [changed, setChanged] = useState<Record<PropertyKey<T>, boolean>>(createKeySet(initialValues, false));
|
|
99
|
+
const [errors, setErrors] = useState<Record<PropertyKey<T>, string>>({} as Record<PropertyKey<T>, string>);
|
|
101
100
|
const [saving, setSaving] = useState(false);
|
|
102
101
|
|
|
103
102
|
//
|
|
104
103
|
// Validation.
|
|
105
104
|
//
|
|
106
105
|
|
|
106
|
+
// TODO(burdon): Validate each property separately.
|
|
107
107
|
const validate = useCallback(
|
|
108
108
|
(values: Partial<T>): values is T => {
|
|
109
109
|
let errors: ValidationError[] = validateSchema(schema, values) ?? [];
|
|
@@ -136,15 +136,8 @@ export const useForm = <T extends BaseObject>({
|
|
|
136
136
|
const canSave = useMemo(
|
|
137
137
|
() =>
|
|
138
138
|
!saving &&
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
([path, isTouched]) =>
|
|
142
|
-
isTouched &&
|
|
143
|
-
Object.keys(errors).some(
|
|
144
|
-
(errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`),
|
|
145
|
-
),
|
|
146
|
-
),
|
|
147
|
-
[touched, errors, saving],
|
|
139
|
+
Object.keys(values).every((property) => touched[property as JsonPath] === false || !errors[property as JsonPath]),
|
|
140
|
+
[values, touched, errors, saving],
|
|
148
141
|
);
|
|
149
142
|
|
|
150
143
|
const handleSave = useCallback(async () => {
|
|
@@ -163,27 +156,19 @@ export const useForm = <T extends BaseObject>({
|
|
|
163
156
|
//
|
|
164
157
|
|
|
165
158
|
const getStatus = useCallback<FormHandler<T>['getStatus']>(
|
|
166
|
-
(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
status: matchingError ? 'error' : undefined,
|
|
175
|
-
error: matchingError ? matchingError[1] : undefined,
|
|
176
|
-
};
|
|
177
|
-
},
|
|
178
|
-
[errors],
|
|
159
|
+
(property: PropertyKey<T>) => ({
|
|
160
|
+
status: errors[property] ? 'error' : undefined,
|
|
161
|
+
error: errors[property] ? errors[property] : undefined,
|
|
162
|
+
}),
|
|
163
|
+
[touched, errors],
|
|
179
164
|
);
|
|
180
165
|
|
|
181
|
-
|
|
182
|
-
|
|
166
|
+
// TODO(burdon): Use path to extract hierarchical value.
|
|
167
|
+
const getFormValue = <V>(property: PropertyKey<T>): V | undefined => {
|
|
168
|
+
return getValue(values, property as any as JsonPath);
|
|
183
169
|
};
|
|
184
170
|
|
|
185
|
-
const onValueChange = (
|
|
186
|
-
const jsonPath = createJsonPath(path);
|
|
171
|
+
const onValueChange = (property: PropertyKey<T>, type: SimpleType, value: any) => {
|
|
187
172
|
let parsedValue = value;
|
|
188
173
|
try {
|
|
189
174
|
if (type === 'number') {
|
|
@@ -194,9 +179,9 @@ export const useForm = <T extends BaseObject>({
|
|
|
194
179
|
parsedValue = undefined;
|
|
195
180
|
}
|
|
196
181
|
|
|
197
|
-
const newValues = { ...setValue(values,
|
|
182
|
+
const newValues = { ...setValue(values, property as any as JsonPath, parsedValue) };
|
|
198
183
|
setValues(newValues);
|
|
199
|
-
setChanged((prev) => ({ ...prev, [
|
|
184
|
+
setChanged((prev) => ({ ...prev, [property]: true }));
|
|
200
185
|
onValuesChanged?.(newValues);
|
|
201
186
|
|
|
202
187
|
const isValid = validate(newValues);
|
|
@@ -205,10 +190,19 @@ export const useForm = <T extends BaseObject>({
|
|
|
205
190
|
}
|
|
206
191
|
};
|
|
207
192
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
193
|
+
// TODO(burdon): This is a leaky abstraction: the hook ideally shouldn't get involved in UX.
|
|
194
|
+
const onBlur = useCallback(
|
|
195
|
+
(event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
196
|
+
const { name } = event.target;
|
|
197
|
+
setTouched((touched) => ({ ...touched, [name]: true }));
|
|
198
|
+
|
|
199
|
+
// TODO(Zan): This should be configurable behavior.
|
|
200
|
+
if (event.relatedTarget?.getAttribute('type') === 'submit') {
|
|
201
|
+
// NOTE: We do this here instead of onSave because the blur event is triggered before the submit event
|
|
202
|
+
// and results in the submit button being disabled when the form is invalid.
|
|
203
|
+
setTouched(createKeySet(values, true));
|
|
204
|
+
}
|
|
205
|
+
|
|
212
206
|
validate(values);
|
|
213
207
|
},
|
|
214
208
|
[validate, values],
|
|
@@ -227,25 +221,23 @@ export const useForm = <T extends BaseObject>({
|
|
|
227
221
|
getStatus,
|
|
228
222
|
getValue: getFormValue,
|
|
229
223
|
onValueChange,
|
|
230
|
-
|
|
224
|
+
onBlur,
|
|
231
225
|
} satisfies FormHandler<T>;
|
|
232
226
|
};
|
|
233
227
|
|
|
234
|
-
const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<
|
|
228
|
+
const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<PropertyKey<T>, V> => {
|
|
235
229
|
invariant(obj);
|
|
236
|
-
return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<
|
|
230
|
+
return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<PropertyKey<T>, V>);
|
|
237
231
|
};
|
|
238
232
|
|
|
239
|
-
const flatMap = (errors: ValidationError[]) => {
|
|
233
|
+
const flatMap = <T extends BaseObject>(errors: ValidationError[]) => {
|
|
240
234
|
return errors.reduce(
|
|
241
235
|
(result, { path, message }) => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!(jsonPath in result)) {
|
|
245
|
-
result[jsonPath] = message;
|
|
236
|
+
if (!(path in result)) {
|
|
237
|
+
result[path as PropertyKey<T>] = message;
|
|
246
238
|
}
|
|
247
239
|
return result;
|
|
248
240
|
},
|
|
249
|
-
{} as Record<
|
|
241
|
+
{} as Record<PropertyKey<T>, string>,
|
|
250
242
|
);
|
|
251
243
|
};
|
package/src/translations.ts
CHANGED
|
@@ -42,7 +42,6 @@ export default [
|
|
|
42
42
|
'format ref': 'Reference',
|
|
43
43
|
'format regex': 'RegExp',
|
|
44
44
|
'format string': 'String',
|
|
45
|
-
'format single-select': 'Select',
|
|
46
45
|
'format text': 'Long text',
|
|
47
46
|
'format time': 'Time',
|
|
48
47
|
'format timestamp': 'Timestamp',
|
|
@@ -54,13 +53,6 @@ export default [
|
|
|
54
53
|
// Placeholders
|
|
55
54
|
'placeholder latitude': 'Latitude',
|
|
56
55
|
'placeholder longitude': 'Longitude',
|
|
57
|
-
|
|
58
|
-
// SelectOptionsInput
|
|
59
|
-
'select option label': 'Label',
|
|
60
|
-
'select option label placeholder': 'Option label',
|
|
61
|
-
'select option color': 'Color',
|
|
62
|
-
'select option delete': 'Delete',
|
|
63
|
-
'select option add': 'Add option',
|
|
64
56
|
},
|
|
65
57
|
},
|
|
66
58
|
},
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { type SchemaProperty } from '@dxos/schema';
|
|
3
|
-
import { type ComponentLookup } from './Form';
|
|
4
|
-
import { type FormInputStateProps } from './FormContext';
|
|
5
|
-
import { type InputComponent } from './Input';
|
|
6
|
-
type ArrayFieldProps = {
|
|
7
|
-
property: SchemaProperty<any>;
|
|
8
|
-
readonly?: boolean;
|
|
9
|
-
inputProps: FormInputStateProps;
|
|
10
|
-
path?: (string | number)[];
|
|
11
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
12
|
-
lookupComponent?: ComponentLookup;
|
|
13
|
-
};
|
|
14
|
-
export declare const ArrayField: ({ property, readonly, path, inputProps, Custom, lookupComponent }: ArrayFieldProps) => React.JSX.Element | null;
|
|
15
|
-
export {};
|
|
16
|
-
//# sourceMappingURL=ArrayField.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ArrayField.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/ArrayField.tsx"],"names":[],"mappings":"AAMA,OAAO,KAA+B,MAAM,OAAO,CAAC;AAOpD,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAExE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAK3D,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,mBAAmB,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IACjD,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,UAAU,sEAAuE,eAAe,6BA8E5G,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormActions.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/FormActions.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,WAAW,2BAA4B,gBAAgB,sBAuBnE,CAAC"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { S } from '@dxos/echo-schema';
|
|
3
|
-
import { type SchemaProperty } from '@dxos/schema';
|
|
4
|
-
import { type ComponentLookup } from './Form';
|
|
5
|
-
import { type InputComponent } from './Input';
|
|
6
|
-
export type FormContentProps = {
|
|
7
|
-
schema: S.Schema.All;
|
|
8
|
-
path?: (string | number)[];
|
|
9
|
-
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
10
|
-
sort?: string[];
|
|
11
|
-
readonly?: boolean;
|
|
12
|
-
lookupComponent?: ComponentLookup;
|
|
13
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
14
|
-
};
|
|
15
|
-
export type FormFieldProps = {
|
|
16
|
-
property: SchemaProperty<any>;
|
|
17
|
-
path?: (string | number)[];
|
|
18
|
-
readonly?: boolean;
|
|
19
|
-
/** Used to indicate if input should be presented inline (e.g. for array items). */
|
|
20
|
-
inline?: boolean;
|
|
21
|
-
lookupComponent?: ComponentLookup;
|
|
22
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
23
|
-
};
|
|
24
|
-
export declare const FormField: ({ property, path, readonly, inline, lookupComponent, Custom }: FormFieldProps) => React.JSX.Element | null;
|
|
25
|
-
export declare const FormFields: ({ schema, path, filter, sort, readonly, lookupComponent, Custom }: FormContentProps) => React.JSX.Element;
|
|
26
|
-
//# sourceMappingURL=FormContent.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormContent.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/FormContent.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAO,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAKxE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IACrB,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,KAAK,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;IACjE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,SAAS,kEAAmE,cAAc,6BAqGtG,CAAC;AAEF,eAAO,MAAM,UAAU,sEAAuE,gBAAgB,sBA4B7G,CAAC"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React, { type FocusEvent, type PropsWithChildren } from 'react';
|
|
2
|
-
import { type BaseObject } from '@dxos/echo-schema';
|
|
3
|
-
import { type SimpleType } from '@dxos/effect';
|
|
4
|
-
import { type FormHandler, type FormOptions } from '../../hooks';
|
|
5
|
-
type FormContextValue<T extends BaseObject> = FormHandler<T>;
|
|
6
|
-
export declare const useFormContext: <T extends BaseObject>() => FormContextValue<T>;
|
|
7
|
-
export declare const useFormValues: (path?: (string | number)[]) => any;
|
|
8
|
-
export type FormInputStateProps = {
|
|
9
|
-
getStatus: () => {
|
|
10
|
-
status?: 'error';
|
|
11
|
-
error?: string;
|
|
12
|
-
};
|
|
13
|
-
getValue: <V>() => V | undefined;
|
|
14
|
-
onValueChange: (type: SimpleType, value: any) => void;
|
|
15
|
-
onBlur: (event: FocusEvent<HTMLElement>) => void;
|
|
16
|
-
};
|
|
17
|
-
export declare const useInputProps: (path?: (string | number)[]) => FormInputStateProps;
|
|
18
|
-
export declare const FormProvider: ({ children, ...formOptions }: PropsWithChildren<FormOptions<any>>) => React.JSX.Element;
|
|
19
|
-
export {};
|
|
20
|
-
//# sourceMappingURL=FormContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormContext.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/FormContext.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAA6B,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAGlG,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAW,MAAM,aAAa,CAAC;AAE1E,KAAK,gBAAgB,CAAC,CAAC,SAAS,UAAU,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;AAI7D,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,UAAU,OAAK,gBAAgB,CAAC,CAAC,CAEzE,CAAC;AAEF,eAAO,MAAM,aAAa,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAQ,GAI9D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACtD,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,aAAa,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAQ,mBAS9D,CAAC;AAEF,eAAO,MAAM,YAAY,iCAAkC,iBAAiB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,sBAG7F,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SelectOptionsInput.d.ts","sourceRoot":"","sources":["../../../../../../src/components/Form/custom/SelectOptionsInput.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAqE,MAAM,OAAO,CAAC;AAU1F,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAGxD,eAAO,MAAM,iBAAiB,kEAAmE,UAAU,sBA8K1G,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SelectOptionsInput.stories.d.ts","sourceRoot":"","sources":["../../../../../../src/components/Form/custom/SelectOptionsInput.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAuC5D,QAAA,MAAM,IAAI,EAAE,IAKX,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC;AAEtB,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC"}
|