@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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 +0 -1
- package/dist/lib/browser/index.mjs +2289 -2269
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2289 -2269
- 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 +8 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +13 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +81 -26
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +37 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +12 -14
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/index.d.ts +12 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +2 -4
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +15 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +10 -6
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +56 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +10 -8
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +16 -16
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +13 -9
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +10 -7
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +3 -3
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +2 -2
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- package/dist/types/src/util/refs.d.ts +1 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -46
- package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -24
- package/src/components/FieldEditor/FieldEditor.tsx +74 -78
- package/src/components/Form/Form.stories.tsx +149 -248
- package/src/components/Form/Form.tsx +347 -107
- package/src/components/Form/FormField.tsx +294 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +172 -0
- package/src/components/Form/FormFieldSet.tsx +123 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +149 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +120 -0
- package/src/components/Form/fields/MarkdownField.tsx +78 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +189 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -16
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +23 -17
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +15 -8
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +40 -37
- package/src/components/ObjectPicker/ObjectPicker.tsx +84 -70
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +230 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +69 -49
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -6
- package/src/components/ViewEditor/ViewEditor.tsx +302 -271
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +12 -7
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +380 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +12 -10
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +11 -17
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- 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/Defaults.d.ts +0 -14
- package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -9
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -63
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -23
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Input.d.ts +0 -29
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -21
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.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.map +0 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -4
- package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/factory.d.ts +0 -8
- package/dist/types/src/components/Form/factory.d.ts.map +0 -1
- package/dist/types/src/hooks/useForm.d.ts +0 -67
- package/dist/types/src/hooks/useForm.d.ts.map +0 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -28
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
- package/dist/types/src/util/schema.d.ts +0 -3
- package/dist/types/src/util/schema.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/ArrayField.tsx +0 -125
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -322
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -269
- package/src/components/Form/custom/GeoPointInput.tsx +0 -80
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/hooks/useForm.ts +0 -295
- package/src/hooks/useQueryRefOptions.ts +0 -59
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -1,119 +1,359 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { type
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
+
import React, { type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
|
|
9
|
+
|
|
10
|
+
import { type AnyProperties } from '@dxos/echo/internal';
|
|
11
|
+
import { createJsonPath, getValue as getValue$ } from '@dxos/effect';
|
|
12
|
+
import {
|
|
13
|
+
IconButton,
|
|
14
|
+
type IconButtonProps,
|
|
15
|
+
ScrollArea,
|
|
16
|
+
type ScrollAreaRootProps,
|
|
17
|
+
type ThemedClassName,
|
|
18
|
+
useTranslation,
|
|
19
|
+
} from '@dxos/react-ui';
|
|
20
|
+
import { mx } from '@dxos/ui-theme';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
type FormHandler,
|
|
24
|
+
type FormHandlerProps,
|
|
25
|
+
type FormUpdateMeta,
|
|
26
|
+
useFormHandler,
|
|
27
|
+
useKeyHandler,
|
|
28
|
+
} from '../../hooks';
|
|
29
|
+
import { translationKey } from '../../translations';
|
|
30
|
+
|
|
31
|
+
import { FormFieldLabel, type FormFieldLabelProps, type FormFieldStateProps } from './FormFieldComponent';
|
|
32
|
+
import {
|
|
33
|
+
FormFieldSet as NaturalFormFieldSet,
|
|
34
|
+
type FormFieldSetProps as NaturalFormFieldSetProps,
|
|
35
|
+
} from './FormFieldSet';
|
|
36
|
+
|
|
37
|
+
// New features/polish
|
|
38
|
+
// [x] Unify readonly/inline modes.
|
|
39
|
+
// [x] Refs
|
|
40
|
+
// [x] Single-select (fix popover)
|
|
41
|
+
// [x] Multi-select (array)
|
|
42
|
+
// [ ] auto save doesn't work for combobox + select due to only firing on blur (workaround is to use onValuesChanged).
|
|
43
|
+
// [ ] Don't call save/autoSave if value hasn't changed.
|
|
44
|
+
// [ ] Fix onCancel (restore values).
|
|
45
|
+
// [ ] Fix useSchema Type.AnyObj cast.
|
|
46
|
+
// [ ] TableCellEditor (handleEnter/ModalController).
|
|
47
|
+
// [ ] Use FormFieldWrapper uniformly
|
|
48
|
+
// [ ] Inline tables.
|
|
49
|
+
// [ ] Defer query until popover.
|
|
50
|
+
// [ ] Omit id from sub properties.
|
|
51
|
+
|
|
52
|
+
// TODO(burdon): Move to @dxos/schema (re-export here).
|
|
53
|
+
export type ExcludeId<S extends Schema.Schema.AnyNoContext> = Omit<Schema.Schema.Type<S>, 'id'>;
|
|
54
|
+
|
|
55
|
+
// TODO(burdon): Move to @dxos/schema (re-export here).
|
|
56
|
+
export const omitId = <S extends Schema.Schema.AnyNoContext>(schema: S): Schema.Schema<ExcludeId<S>, ExcludeId<S>> =>
|
|
57
|
+
schema.pipe(Schema.omit('id')) as any;
|
|
58
|
+
|
|
59
|
+
//
|
|
60
|
+
// Context
|
|
61
|
+
//
|
|
62
|
+
|
|
63
|
+
type FormContextValue<T extends AnyProperties = any> = {
|
|
64
|
+
/**
|
|
65
|
+
* Form handler.
|
|
66
|
+
*/
|
|
67
|
+
form: FormHandler<T>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Show debug info.
|
|
71
|
+
*/
|
|
72
|
+
debug?: boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Testing.
|
|
76
|
+
*/
|
|
77
|
+
testId?: string;
|
|
78
|
+
} & Pick<NaturalFormFieldSetProps<T>, 'readonly' | 'layout' | 'fieldMap' | 'fieldProvider' | 'projection'>;
|
|
79
|
+
|
|
80
|
+
const [FormContextProvider, useFormContext] = createContext<FormContextValue>('Form');
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the current form values.
|
|
84
|
+
*/
|
|
85
|
+
const useFormValues: {
|
|
86
|
+
<T extends AnyProperties>(componentName: string, path?: (string | number)[]): T;
|
|
87
|
+
<T extends AnyProperties>(
|
|
88
|
+
componentName: string,
|
|
89
|
+
path: (string | number)[] | undefined,
|
|
90
|
+
defaultValue: () => T,
|
|
91
|
+
): T | undefined;
|
|
92
|
+
} = (componentName: string, path?: (string | number)[], defaultValue?: () => any) => {
|
|
93
|
+
const stablePath = useMemo(() => path ?? [], [path ? path.join('.') : undefined]);
|
|
94
|
+
const jsonPath = createJsonPath(stablePath);
|
|
95
|
+
const {
|
|
96
|
+
form: { values, onValueChange },
|
|
97
|
+
} = useFormContext(componentName);
|
|
98
|
+
|
|
99
|
+
const value = getValue$(values, jsonPath);
|
|
100
|
+
|
|
101
|
+
// Apply default value once when the field has no value. lastAppliedPathRef prevents
|
|
102
|
+
// re-applying on every render (e.g. when defaultValue() returns null) and ensures
|
|
103
|
+
// we apply per path when the hook is used for different fields.
|
|
104
|
+
const lastAppliedPathRef = useRef<string | null>(null);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (value == null && defaultValue && lastAppliedPathRef.current !== jsonPath) {
|
|
107
|
+
lastAppliedPathRef.current = jsonPath;
|
|
108
|
+
onValueChange(stablePath, SchemaAST.stringKeyword, defaultValue());
|
|
109
|
+
}
|
|
110
|
+
}, [value, defaultValue, onValueChange, stablePath, jsonPath]);
|
|
111
|
+
|
|
112
|
+
return value;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get the state props for the given field.
|
|
117
|
+
*/
|
|
118
|
+
const useFormFieldState = (componentName: string, path: (string | number)[] = []): FormFieldStateProps => {
|
|
119
|
+
const stablePath = useMemo(() => path, [Array.isArray(path) ? path.join('.') : path]);
|
|
120
|
+
const {
|
|
121
|
+
form: { getStatus, getValue, onBlur, onValueChange },
|
|
122
|
+
} = useFormContext(componentName);
|
|
123
|
+
|
|
124
|
+
return useMemo(
|
|
125
|
+
() => ({
|
|
126
|
+
getStatus: () => getStatus(stablePath),
|
|
127
|
+
getValue: () => getValue(stablePath),
|
|
128
|
+
onBlur: () => onBlur(stablePath),
|
|
129
|
+
onValueChange: (ast: SchemaAST.AST, value: any) => onValueChange(stablePath, ast, value),
|
|
130
|
+
}),
|
|
131
|
+
[getStatus, getValue, onBlur, onValueChange, stablePath],
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
//
|
|
136
|
+
// Root
|
|
137
|
+
//
|
|
138
|
+
|
|
139
|
+
type FormRootProps<T extends AnyProperties = AnyProperties> = PropsWithChildren<
|
|
140
|
+
{
|
|
141
|
+
/**
|
|
142
|
+
* Called when the form is submitted and passes validation.
|
|
143
|
+
*/
|
|
144
|
+
onSave?: (values: T, meta: FormUpdateMeta<T>) => void;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Called when the form is canceled to abandon/undo any pending changes.
|
|
148
|
+
*/
|
|
149
|
+
onCancel?: () => void;
|
|
150
|
+
} & Omit<FormContextValue<T>, 'form'> &
|
|
151
|
+
Pick<FormHandlerProps<T>, 'schema' | 'autoSave' | 'values' | 'defaultValues' | 'onValidate' | 'onValuesChanged'> &
|
|
152
|
+
Omit<NaturalFormFieldSetProps<T>, 'schema' | 'path'>
|
|
153
|
+
>;
|
|
154
|
+
|
|
155
|
+
const FormRoot = <T extends AnyProperties = AnyProperties>({
|
|
156
|
+
children,
|
|
54
157
|
schema,
|
|
55
|
-
|
|
56
|
-
onValuesChanged,
|
|
57
|
-
onValidate,
|
|
158
|
+
values,
|
|
58
159
|
onSave,
|
|
160
|
+
onCancel,
|
|
59
161
|
...props
|
|
60
|
-
}:
|
|
61
|
-
const
|
|
162
|
+
}: FormRootProps<T>) => {
|
|
163
|
+
const form = useFormHandler({ schema, values, onSave, onCancel, ...props });
|
|
62
164
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const firstFocusable = findFirstFocusable(formRef.current);
|
|
68
|
-
if (firstFocusable) {
|
|
69
|
-
firstFocusable.focus();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}, [autoFocus]);
|
|
73
|
-
|
|
74
|
-
// TODO(burdon): Name?
|
|
75
|
-
const handleValid = useCallback(
|
|
76
|
-
async (values: any, meta: { changed: FormHandler<T>['changed'] }) => {
|
|
77
|
-
if (autoSave) {
|
|
78
|
-
await onSave?.(values, meta);
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
[autoSave, onSave],
|
|
165
|
+
return (
|
|
166
|
+
<FormContextProvider form={form} {...props}>
|
|
167
|
+
{children}
|
|
168
|
+
</FormContextProvider>
|
|
82
169
|
);
|
|
170
|
+
};
|
|
83
171
|
|
|
172
|
+
FormRoot.displayName = 'Form.Root';
|
|
173
|
+
|
|
174
|
+
//
|
|
175
|
+
// Viewport
|
|
176
|
+
//
|
|
177
|
+
|
|
178
|
+
const FORM_VIEWPORT_NAME = 'Form.Viewport';
|
|
179
|
+
|
|
180
|
+
type FormViewportProps = PropsWithChildren<ScrollAreaRootProps>;
|
|
181
|
+
|
|
182
|
+
const FormViewport = ({
|
|
183
|
+
children,
|
|
184
|
+
classNames,
|
|
185
|
+
margin = true,
|
|
186
|
+
padding = true,
|
|
187
|
+
thin = true,
|
|
188
|
+
...props
|
|
189
|
+
}: FormViewportProps) => {
|
|
84
190
|
return (
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
onValid={handleValid}
|
|
93
|
-
onSave={onSave}
|
|
191
|
+
<ScrollArea.Root
|
|
192
|
+
orientation='vertical'
|
|
193
|
+
classNames={classNames}
|
|
194
|
+
margin={margin}
|
|
195
|
+
padding={padding}
|
|
196
|
+
thin={thin}
|
|
197
|
+
{...props}
|
|
94
198
|
>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
199
|
+
<ScrollArea.Viewport>{children}</ScrollArea.Viewport>
|
|
200
|
+
</ScrollArea.Root>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
FormViewport.displayName = FORM_VIEWPORT_NAME;
|
|
205
|
+
|
|
206
|
+
//
|
|
207
|
+
// Content
|
|
208
|
+
//
|
|
209
|
+
|
|
210
|
+
const FORM_CONTENT_NAME = 'Form.Content';
|
|
211
|
+
|
|
212
|
+
type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
|
|
213
|
+
|
|
214
|
+
const FormContent = ({ classNames, children }: FormContentProps) => {
|
|
215
|
+
const { form, testId } = useFormContext(FORM_CONTENT_NAME);
|
|
216
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
217
|
+
useKeyHandler(ref.current, form);
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div ref={ref} role='form' className={mx('w-full flex flex-col gap-form-padding', classNames)} data-testid={testId}>
|
|
221
|
+
{children}
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
FormContent.displayName = FORM_CONTENT_NAME;
|
|
227
|
+
|
|
228
|
+
//
|
|
229
|
+
// FieldSet
|
|
230
|
+
//
|
|
231
|
+
|
|
232
|
+
const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
233
|
+
|
|
234
|
+
type FormFieldSetProps = ThemedClassName<NaturalFormFieldSetProps<any>>;
|
|
235
|
+
|
|
236
|
+
const FormFieldSet = (props: FormFieldSetProps) => {
|
|
237
|
+
const { form, ...contextProps } = useFormContext(FORM_FIELDSET_NAME);
|
|
238
|
+
|
|
239
|
+
return <NaturalFormFieldSet schema={form.schema} {...contextProps} {...props} />;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
FormFieldSet.displayName = FORM_FIELDSET_NAME;
|
|
243
|
+
|
|
244
|
+
//
|
|
245
|
+
// Actions
|
|
246
|
+
//
|
|
247
|
+
|
|
248
|
+
const FORM_ACTIONS_NAME = 'Form.Actions';
|
|
249
|
+
|
|
250
|
+
type FormActionsProps = ThemedClassName<{}>;
|
|
251
|
+
|
|
252
|
+
const FormActions = ({ classNames }: FormActionsProps) => {
|
|
253
|
+
const { t } = useTranslation(translationKey);
|
|
254
|
+
const {
|
|
255
|
+
form: { canSave, onSave, onCancel },
|
|
256
|
+
readonly,
|
|
257
|
+
layout,
|
|
258
|
+
} = useFormContext(FORM_ACTIONS_NAME);
|
|
259
|
+
|
|
260
|
+
if (readonly || layout === 'static') {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// TODO(burdon): Currently onCancel is a no-op; implement "revert values".
|
|
265
|
+
// Deprecate FormSubmit ans use FormActions without Cancel button if no callback is supplied.
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div role='none' className={mx('grid grid-flow-col gap-2 auto-cols-fr py-form-padding', classNames)}>
|
|
269
|
+
{onCancel && (
|
|
270
|
+
<IconButton
|
|
271
|
+
icon='ph--x--regular'
|
|
272
|
+
iconEnd
|
|
273
|
+
label={t('cancel button label')}
|
|
274
|
+
onClick={onCancel}
|
|
275
|
+
data-testid='cancel-button'
|
|
276
|
+
/>
|
|
277
|
+
)}
|
|
278
|
+
{onSave && (
|
|
279
|
+
<IconButton
|
|
280
|
+
type='submit'
|
|
281
|
+
variant='primary'
|
|
282
|
+
disabled={!canSave}
|
|
283
|
+
icon='ph--check--regular'
|
|
284
|
+
iconEnd
|
|
285
|
+
label={t('save button label')}
|
|
286
|
+
onClick={onSave}
|
|
287
|
+
data-testid='save-button'
|
|
112
288
|
/>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)}
|
|
116
|
-
</div>
|
|
117
|
-
</FormProvider>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
118
291
|
);
|
|
119
292
|
};
|
|
293
|
+
|
|
294
|
+
FormActions.displayName = FORM_ACTIONS_NAME;
|
|
295
|
+
|
|
296
|
+
//
|
|
297
|
+
// Submit
|
|
298
|
+
//
|
|
299
|
+
|
|
300
|
+
const FORM_SUBMIT_NAME = 'Form.Submit';
|
|
301
|
+
|
|
302
|
+
type FormSubmitProps = ThemedClassName<Partial<Pick<IconButtonProps, 'icon' | 'label' | 'disabled'>>>;
|
|
303
|
+
|
|
304
|
+
const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
|
|
305
|
+
const { t } = useTranslation(translationKey);
|
|
306
|
+
const {
|
|
307
|
+
form: { canSave, onSave },
|
|
308
|
+
readonly,
|
|
309
|
+
layout,
|
|
310
|
+
} = useFormContext(FORM_SUBMIT_NAME);
|
|
311
|
+
|
|
312
|
+
if (readonly || layout === 'static') {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<div role='none' className={mx('flex w-full pt-form-padding', classNames)}>
|
|
318
|
+
<IconButton
|
|
319
|
+
classNames='w-full'
|
|
320
|
+
type='submit'
|
|
321
|
+
variant='primary'
|
|
322
|
+
disabled={disabled ?? !canSave}
|
|
323
|
+
icon={icon ?? 'ph--check--regular'}
|
|
324
|
+
label={label ?? t('save button label')}
|
|
325
|
+
onClick={onSave}
|
|
326
|
+
data-testid='save-button'
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
FormSubmit.displayName = FORM_SUBMIT_NAME;
|
|
333
|
+
|
|
334
|
+
//
|
|
335
|
+
// Form
|
|
336
|
+
// https://www.radix-ui.com/primitives/docs/guides/composition
|
|
337
|
+
//
|
|
338
|
+
|
|
339
|
+
export const Form = {
|
|
340
|
+
Root: FormRoot,
|
|
341
|
+
Viewport: FormViewport,
|
|
342
|
+
Content: FormContent,
|
|
343
|
+
FieldSet: FormFieldSet,
|
|
344
|
+
Label: FormFieldLabel,
|
|
345
|
+
Actions: FormActions,
|
|
346
|
+
Submit: FormSubmit,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export { useFormContext, useFormValues, useFormFieldState };
|
|
350
|
+
|
|
351
|
+
export type {
|
|
352
|
+
FormRootProps,
|
|
353
|
+
FormViewportProps,
|
|
354
|
+
FormContentProps,
|
|
355
|
+
FormFieldSetProps,
|
|
356
|
+
FormFieldLabelProps,
|
|
357
|
+
FormActionsProps,
|
|
358
|
+
FormSubmitProps,
|
|
359
|
+
};
|