@dxos/react-ui-form 0.7.1-staging.599df14

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.
Files changed (93) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +4 -0
  3. package/dist/lib/browser/index.mjs +1008 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node/index.cjs +1028 -0
  7. package/dist/lib/node/index.cjs.map +7 -0
  8. package/dist/lib/node/meta.json +1 -0
  9. package/dist/lib/node-esm/index.mjs +1010 -0
  10. package/dist/lib/node-esm/index.mjs.map +7 -0
  11. package/dist/lib/node-esm/meta.json +1 -0
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +16 -0
  13. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -0
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +9 -0
  15. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -0
  16. package/dist/types/src/components/FieldEditor/index.d.ts +2 -0
  17. package/dist/types/src/components/FieldEditor/index.d.ts.map +1 -0
  18. package/dist/types/src/components/Form/Custom.d.ts +4 -0
  19. package/dist/types/src/components/Form/Custom.d.ts.map +1 -0
  20. package/dist/types/src/components/Form/Defaults.d.ts +7 -0
  21. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -0
  22. package/dist/types/src/components/Form/Form.d.ts +26 -0
  23. package/dist/types/src/components/Form/Form.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/Form.stories.d.ts +38 -0
  25. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/Input.d.ts +29 -0
  27. package/dist/types/src/components/Form/Input.d.ts.map +1 -0
  28. package/dist/types/src/components/Form/factory.d.ts +8 -0
  29. package/dist/types/src/components/Form/factory.d.ts.map +1 -0
  30. package/dist/types/src/components/Form/index.d.ts +5 -0
  31. package/dist/types/src/components/Form/index.d.ts.map +1 -0
  32. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +10 -0
  33. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +18 -0
  35. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -0
  36. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +8 -0
  37. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -0
  38. package/dist/types/src/components/ViewEditor/index.d.ts +2 -0
  39. package/dist/types/src/components/ViewEditor/index.d.ts.map +1 -0
  40. package/dist/types/src/components/deprecated/Form.d.ts +15 -0
  41. package/dist/types/src/components/deprecated/Form.d.ts.map +1 -0
  42. package/dist/types/src/components/deprecated/Form.stories.d.ts +13 -0
  43. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -0
  44. package/dist/types/src/components/deprecated/FormInput.d.ts +11 -0
  45. package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -0
  46. package/dist/types/src/components/deprecated/index.d.ts +2 -0
  47. package/dist/types/src/components/deprecated/index.d.ts.map +1 -0
  48. package/dist/types/src/components/index.d.ts +5 -0
  49. package/dist/types/src/components/index.d.ts.map +1 -0
  50. package/dist/types/src/components/testing.d.ts +7 -0
  51. package/dist/types/src/components/testing.d.ts.map +1 -0
  52. package/dist/types/src/hooks/index.d.ts +2 -0
  53. package/dist/types/src/hooks/index.d.ts.map +1 -0
  54. package/dist/types/src/hooks/useForm.d.ts +58 -0
  55. package/dist/types/src/hooks/useForm.d.ts.map +1 -0
  56. package/dist/types/src/index.d.ts +6 -0
  57. package/dist/types/src/index.d.ts.map +1 -0
  58. package/dist/types/src/translations.d.ts +46 -0
  59. package/dist/types/src/translations.d.ts.map +1 -0
  60. package/dist/types/src/util/field.d.ts +22 -0
  61. package/dist/types/src/util/field.d.ts.map +1 -0
  62. package/dist/types/src/util/formatting.d.ts +15 -0
  63. package/dist/types/src/util/formatting.d.ts.map +1 -0
  64. package/dist/types/src/util/index.d.ts +3 -0
  65. package/dist/types/src/util/index.d.ts.map +1 -0
  66. package/package.json +76 -0
  67. package/src/components/FieldEditor/FieldEditor.stories.tsx +56 -0
  68. package/src/components/FieldEditor/FieldEditor.tsx +167 -0
  69. package/src/components/FieldEditor/index.ts +5 -0
  70. package/src/components/Form/Custom.tsx +65 -0
  71. package/src/components/Form/Defaults.tsx +130 -0
  72. package/src/components/Form/Form.stories.tsx +137 -0
  73. package/src/components/Form/Form.tsx +176 -0
  74. package/src/components/Form/Input.tsx +56 -0
  75. package/src/components/Form/factory.tsx +32 -0
  76. package/src/components/Form/index.ts +8 -0
  77. package/src/components/ViewEditor/Test.stories.tsx +45 -0
  78. package/src/components/ViewEditor/ViewEditor.stories.tsx +73 -0
  79. package/src/components/ViewEditor/ViewEditor.tsx +158 -0
  80. package/src/components/ViewEditor/index.ts +5 -0
  81. package/src/components/deprecated/Form.stories.tsx +62 -0
  82. package/src/components/deprecated/Form.tsx +119 -0
  83. package/src/components/deprecated/FormInput.tsx +58 -0
  84. package/src/components/deprecated/index.ts +5 -0
  85. package/src/components/index.ts +9 -0
  86. package/src/components/testing.tsx +24 -0
  87. package/src/hooks/index.ts +5 -0
  88. package/src/hooks/useForm.ts +229 -0
  89. package/src/index.ts +11 -0
  90. package/src/translations.ts +59 -0
  91. package/src/util/field.ts +137 -0
  92. package/src/util/formatting.ts +132 -0
  93. package/src/util/index.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../../src/components/testing.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAEtD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItD,eAAO,MAAM,SAAS,6BAA8B,eAAe,CAAC,iBAAiB,CAAC,sBAIrF,CAAC;AAEF,eAAO,MAAM,UAAU,mCAAoC,eAAe,CAAC,iBAAiB,CAAC;IAAE,IAAI,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC,sBAO5G,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './useForm';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { type FocusEvent } from 'react';
2
+ import { type SimpleType, type S } from '@dxos/effect';
3
+ import { type PropertyKey, type ValidationError } from '@dxos/schema';
4
+ /**
5
+ * Return type from `useForm` hook.
6
+ */
7
+ export type FormHandler<T extends object = {}> = {
8
+ values: T;
9
+ errors: Record<PropertyKey<T>, string>;
10
+ touched: Record<PropertyKey<T>, boolean>;
11
+ changed: Record<PropertyKey<T>, boolean>;
12
+ canSubmit: boolean;
13
+ handleSubmit: () => void;
14
+ getStatus: (property: PropertyKey<T>) => {
15
+ status?: 'error';
16
+ error?: string;
17
+ };
18
+ getValue: <V>(property: PropertyKey<T>) => V;
19
+ onValueChange: <V>(property: PropertyKey<T>, type: SimpleType, value: V) => void;
20
+ onBlur: (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
21
+ };
22
+ /**
23
+ * Hook options.
24
+ */
25
+ export interface FormOptions<T extends object> {
26
+ schema: S.Schema<T>;
27
+ initialValues: T;
28
+ /**
29
+ * Callback for value changes. Note: This is called even when values are invalid.
30
+ * Sometimes the parent component may want to know about changes even if the form is
31
+ * in an invalid state.
32
+ */
33
+ onValuesChanged?: (values: T) => void;
34
+ /**
35
+ * Custom validation function that runs only after schema validation passes.
36
+ * Use this for complex validation logic that can't be expressed in the schema.
37
+ * @returns Array of validation errors, or undefined if validation passes
38
+ */
39
+ onValidate?: (values: T) => ValidationError[] | undefined;
40
+ /**
41
+ * Called when the form is valid.
42
+ */
43
+ onValid?: (values: T, meta: {
44
+ changed: FormHandler<T>['changed'];
45
+ }) => void;
46
+ /**
47
+ * Called when the form is submitted and passes validation.
48
+ */
49
+ onSubmit?: (values: T, meta: {
50
+ changed: FormHandler<T>['changed'];
51
+ }) => void;
52
+ }
53
+ /**
54
+ * Creates a hook for managing form state, including values, validation, and submission.
55
+ * Deeply integrated with `@dxos/schema` for schema-based validation.
56
+ */
57
+ export declare const useForm: <T extends object>({ schema, initialValues, onValuesChanged, onValidate, onValid, onSubmit, }: FormOptions<T>) => FormHandler<T>;
58
+ //# sourceMappingURL=useForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useForm.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useForm.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAA6C,MAAM,OAAO,CAAC;AAEnF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,cAAc,CAAC;AAGvD,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,EAAE,IAAI;IAK/C,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IAMzB,SAAS,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9E,QAAQ,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC7C,aAAa,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACjF,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,IAAI,CAAC;CAC7E,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM;IAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAGpB,aAAa,EAAE,CAAC,CAAC;IAEjB;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAEtC;;;;OAIG;IAEH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,eAAe,EAAE,GAAG,SAAS,CAAC;IAE1D;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAE5E;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,8EAOrC,WAAW,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CAiIhC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import translations from './translations';
2
+ export * from './components';
3
+ export * from './hooks';
4
+ export * from './util';
5
+ export { translations };
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAE1C,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AAEvB,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,46 @@
1
+ export declare const translationKey = "react-ui-form";
2
+ declare const _default: {
3
+ 'en-US': {
4
+ "react-ui-form": {
5
+ 'fields label': string;
6
+ 'button add property': string;
7
+ 'field property label': string;
8
+ 'field property placeholder': string;
9
+ 'field format label': string;
10
+ 'field path label': string;
11
+ 'add field': string;
12
+ 'delete field': string;
13
+ 'field limit reached': string;
14
+ 'button cancel': string;
15
+ 'button save': string;
16
+ 'format boolean': string;
17
+ 'format currency': string;
18
+ 'format date': string;
19
+ 'format date-time': string;
20
+ 'format did': string;
21
+ 'format duration': string;
22
+ 'format email': string;
23
+ 'format formula': string;
24
+ 'format hostname': string;
25
+ 'format integer': string;
26
+ 'format json': string;
27
+ 'format latlng': string;
28
+ 'format markdown': string;
29
+ 'format percent': string;
30
+ 'format ref': string;
31
+ 'format regex': string;
32
+ 'format string': string;
33
+ 'format text': string;
34
+ 'format time': string;
35
+ 'format timestamp': string;
36
+ 'format user': string;
37
+ 'format number': string;
38
+ 'format uri': string;
39
+ 'format uuid': string;
40
+ 'placeholder latitude': string;
41
+ 'placeholder longitude': string;
42
+ };
43
+ };
44
+ }[];
45
+ export default _default;
46
+ //# sourceMappingURL=translations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE9C,wBAoDE"}
@@ -0,0 +1,22 @@
1
+ import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
2
+ import { type ValidationError } from '@dxos/schema';
3
+ /**
4
+ * Parse value by field value type.
5
+ * Used by Table, Sheet, etc.
6
+ * Handles various data types including booleans, numbers, dates, and strings.
7
+ * Returns undefined for empty or null inputs.
8
+ */
9
+ export type ParseProps = {
10
+ type?: TypeEnum;
11
+ format?: FormatEnum;
12
+ value: any;
13
+ };
14
+ export declare const parseValue: ({ type, format, value }: ParseProps) => any;
15
+ export type CellClassesForFieldTypeProps = {
16
+ type?: TypeEnum;
17
+ format?: FormatEnum;
18
+ };
19
+ export declare const cellClassesForFieldType: ({ type, format }: CellClassesForFieldTypeProps) => string[] | undefined;
20
+ export declare const cellClassesForRowSelection: (selected: boolean) => string[] | undefined;
21
+ export declare const pathNotUniqueError: (path: string) => ValidationError;
22
+ //# sourceMappingURL=field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../../src/util/field.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,KAAK,EAAE,GAAG,CAAC;CACZ,CAAC;AAEF,eAAO,MAAM,UAAU,4BAA6B,UAAU,QAoE7D,CAAC;AAGF,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,uBAAuB,qBAAsB,4BAA4B,KAAG,MAAM,EAAE,GAAG,SAiCnG,CAAC;AAEF,eAAO,MAAM,0BAA0B,aAAc,OAAO,yBAAsD,CAAC;AAGnH,eAAO,MAAM,kBAAkB,SAAU,MAAM,KAAG,eAGhD,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
2
+ type ValueFormatProps = {
3
+ type: TypeEnum;
4
+ format?: FormatEnum | undefined;
5
+ value: any;
6
+ locale?: string | undefined;
7
+ };
8
+ /**
9
+ * Format value by type.
10
+ * Used by Table, Sheet, etc.
11
+ */
12
+ export declare const formatForDisplay: ({ type, format, value, locale }: ValueFormatProps) => string;
13
+ export declare const formatForEditing: ({ type, format, value, locale }: ValueFormatProps) => string;
14
+ export {};
15
+ //# sourceMappingURL=formatting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../../../../src/util/formatting.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEzD,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF;;;GAGG;AAGH,eAAO,MAAM,gBAAgB,oCAAiD,gBAAgB,KAAG,MA+DhG,CAAC;AAEF,eAAO,MAAM,gBAAgB,oCAAiD,gBAAgB,KAAG,MA+ChG,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './field';
2
+ export * from './formatting';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@dxos/react-ui-form",
3
+ "version": "0.7.1-staging.599df14",
4
+ "description": "Data components.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/types/src/index.d.ts",
12
+ "browser": "./dist/lib/browser/index.mjs",
13
+ "node": "./dist/lib/node-esm/index.mjs"
14
+ }
15
+ },
16
+ "types": "dist/types/src/index.d.ts",
17
+ "typesVersions": {
18
+ "*": {}
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "dependencies": {
25
+ "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
26
+ "@atlaskit/pragmatic-drag-and-drop-flourish": "^1.1.2",
27
+ "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
28
+ "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^1.1.3",
29
+ "@effect/schema": "^0.75.5",
30
+ "@radix-ui/react-context": "^1.0.0",
31
+ "date-fns": "^3.3.1",
32
+ "effect": "^3.9.2",
33
+ "jsonpath": "^1.1.1",
34
+ "@dxos/async": "0.7.1-staging.599df14",
35
+ "@dxos/echo-db": "0.7.1-staging.599df14",
36
+ "@dxos/echo-schema": "0.7.1-staging.599df14",
37
+ "@dxos/debug": "0.7.1-staging.599df14",
38
+ "@dxos/effect": "0.7.1-staging.599df14",
39
+ "@dxos/invariant": "0.7.1-staging.599df14",
40
+ "@dxos/log": "0.7.1-staging.599df14",
41
+ "@dxos/keys": "0.7.1-staging.599df14",
42
+ "@dxos/react-client": "0.7.1-staging.599df14",
43
+ "@dxos/react-ui-list": "0.7.1-staging.599df14",
44
+ "@dxos/react-ui-types": "0.7.1-staging.599df14",
45
+ "@dxos/schema": "0.7.1-staging.599df14",
46
+ "@dxos/util": "0.7.1-staging.599df14"
47
+ },
48
+ "devDependencies": {
49
+ "@phosphor-icons/react": "^2.1.5",
50
+ "@types/jsonpath": "~0.2.4",
51
+ "@types/react": "~18.2.0",
52
+ "@types/react-dom": "~18.2.0",
53
+ "fast-check": "^3.19.0",
54
+ "react": "~18.2.0",
55
+ "react-dom": "~18.2.0",
56
+ "vite": "5.4.7",
57
+ "@dxos/echo-signals": "0.7.1-staging.599df14",
58
+ "@dxos/react-ui": "0.7.1-staging.599df14",
59
+ "@dxos/react-ui-syntax-highlighter": "0.7.1-staging.599df14",
60
+ "@dxos/react-ui-theme": "0.7.1-staging.599df14",
61
+ "@dxos/storybook-utils": "0.7.1-staging.599df14",
62
+ "@dxos/random": "0.7.1-staging.599df14"
63
+ },
64
+ "peerDependencies": {
65
+ "@effect/schema": "^0.75.5",
66
+ "@phosphor-icons/react": "^2.1.5",
67
+ "effect": "^3.9.2",
68
+ "react": "~18.2.0",
69
+ "react-dom": "~18.2.0",
70
+ "@dxos/react-ui-theme": "0.7.1-staging.599df14",
71
+ "@dxos/react-ui": "0.7.1-staging.599df14"
72
+ },
73
+ "publishConfig": {
74
+ "access": "public"
75
+ }
76
+ }
@@ -0,0 +1,56 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react';
8
+ import React from 'react';
9
+
10
+ import { createMutableSchema } from '@dxos/echo-schema/testing';
11
+ import { log } from '@dxos/log';
12
+ import { ViewProjection } from '@dxos/schema';
13
+ import { TestSchema, testView } from '@dxos/schema/testing';
14
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
15
+
16
+ import { FieldEditor, type FieldEditorProps } from './FieldEditor';
17
+ import translations from '../../translations';
18
+ import { TestLayout, TestPanel } from '../testing';
19
+
20
+ type StoryProps = FieldEditorProps;
21
+
22
+ const DefaultStory = (props: FieldEditorProps) => {
23
+ const handleComplete: FieldEditorProps['onClose'] = () => {
24
+ log.info('onClose', { props });
25
+ };
26
+
27
+ return (
28
+ <TestLayout json={{ props }}>
29
+ <TestPanel>
30
+ <FieldEditor {...props} onClose={handleComplete} />
31
+ </TestPanel>
32
+ </TestLayout>
33
+ );
34
+ };
35
+
36
+ const meta: Meta<StoryProps> = {
37
+ title: 'ui/react-ui-form/FieldEditor',
38
+ component: FieldEditor,
39
+ render: DefaultStory,
40
+ decorators: [withLayout({ fullscreen: true }), withTheme],
41
+ parameters: {
42
+ translations,
43
+ },
44
+ };
45
+
46
+ export default meta;
47
+
48
+ type Story = StoryObj<StoryProps>;
49
+
50
+ export const Default: Story = {
51
+ args: {
52
+ projection: new ViewProjection(createMutableSchema(TestSchema), testView),
53
+ view: testView,
54
+ field: testView.fields[0],
55
+ },
56
+ };
@@ -0,0 +1,167 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useEffect, useState } from 'react';
6
+
7
+ import { type SchemaResolver } from '@dxos/echo-db';
8
+ import { FormatEnum, FormatEnums, formatToType, type MutableSchema } from '@dxos/echo-schema';
9
+ import { log } from '@dxos/log';
10
+ import { useTranslation } from '@dxos/react-ui';
11
+ import {
12
+ getFormatSchema,
13
+ getSchemaProperties,
14
+ sortProperties,
15
+ type FieldType,
16
+ type PropertyType,
17
+ type ViewType,
18
+ type ViewProjection,
19
+ } from '@dxos/schema';
20
+
21
+ import { translationKey } from '../../translations';
22
+ import { Form, type FormProps, SelectInput } from '../Form';
23
+
24
+ export type FieldEditorProps = {
25
+ view: ViewType;
26
+ projection: ViewProjection;
27
+ field: FieldType;
28
+ registry?: SchemaResolver;
29
+ onClose: () => void;
30
+ onCancel?: () => void;
31
+ };
32
+
33
+ /**
34
+ * Displays a Form representing the metadata for a given `Field` and `View`.
35
+ */
36
+ export const FieldEditor = ({ view, projection, field, registry, onClose, onCancel }: FieldEditorProps) => {
37
+ const { t } = useTranslation(translationKey);
38
+ const [props, setProps] = useState<PropertyType>(projection.getFieldProjection(field.id).props);
39
+ useEffect(() => setProps(projection.getFieldProjection(field.id).props), [field, projection]);
40
+
41
+ const [schemas, setSchemas] = useState<MutableSchema[]>([]);
42
+ useEffect(() => {
43
+ if (!registry) {
44
+ return;
45
+ }
46
+
47
+ const subscription = registry.subscribe(setSchemas);
48
+ const t = setTimeout(async () => {
49
+ const schemas = await registry.query();
50
+ setSchemas(schemas);
51
+ });
52
+ return () => {
53
+ clearTimeout(t);
54
+ subscription?.();
55
+ };
56
+ }, [registry]);
57
+
58
+ const [schema, setSchema] = useState<MutableSchema>();
59
+ useEffect(() => {
60
+ setSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
61
+ }, [schemas, props?.referenceSchema]);
62
+
63
+ // TODO(burdon): Need to wrap otherwise throws error:
64
+ // Class constructor SchemaClass cannot be invoked without 'new'.
65
+ const [{ fieldSchema }, setFieldSchema] = useState({ fieldSchema: getFormatSchema(props?.format) });
66
+ const handleValuesChanged = useCallback<NonNullable<FormProps<PropertyType>['onValuesChanged']>>(
67
+ (_props) => {
68
+ // Update schema if format changed.
69
+ // TODO(burdon): Callback should pass `changed` to indicate which fields have changed.
70
+ if (_props.format !== props.format) {
71
+ setFieldSchema({ fieldSchema: getFormatSchema(_props.format) });
72
+ }
73
+ if (_props.referenceSchema !== props.referenceSchema) {
74
+ setSchema(schemas.find((schema) => schema.typename === _props.referenceSchema));
75
+ }
76
+
77
+ setProps((props) => {
78
+ const type = formatToType[_props.format as keyof typeof formatToType];
79
+ if (props.type !== type) {
80
+ return { ...props, ..._props, type };
81
+ }
82
+
83
+ return props;
84
+ });
85
+ },
86
+ [schemas, props],
87
+ );
88
+
89
+ const handleValidate = useCallback<NonNullable<FormProps<PropertyType>['onValidate']>>(
90
+ ({ property }) => {
91
+ if (property && view.fields.find((f) => f.path === property && f.path !== field.path)) {
92
+ return [
93
+ {
94
+ path: 'property',
95
+ message: `property is not unique: '${property}'`,
96
+ },
97
+ ];
98
+ }
99
+ },
100
+ [view.fields, field],
101
+ );
102
+
103
+ const handleSubmit = useCallback<NonNullable<FormProps<PropertyType>['onSubmit']>>(
104
+ (props) => {
105
+ projection.setFieldProjection({ field, props });
106
+ onClose();
107
+ },
108
+ [projection, field, onClose],
109
+ );
110
+
111
+ const handleCancel = useCallback<NonNullable<FormProps<PropertyType>['onCancel']>>(() => {
112
+ // Need to defer to allow form to close.
113
+ requestAnimationFrame(() => onCancel?.());
114
+ onClose();
115
+ }, [onClose]);
116
+
117
+ if (!fieldSchema) {
118
+ log.warn('invalid format', { props });
119
+ return null;
120
+ }
121
+
122
+ return (
123
+ <Form<PropertyType>
124
+ key={field.id}
125
+ autoFocus
126
+ values={props}
127
+ schema={fieldSchema}
128
+ filter={(props) => props.filter((p) => p.name !== 'type')}
129
+ sort={['property', 'format']}
130
+ onValuesChanged={handleValuesChanged}
131
+ onValidate={handleValidate}
132
+ onSubmit={handleSubmit}
133
+ onCancel={handleCancel}
134
+ Custom={{
135
+ ['format' satisfies keyof PropertyType]: (props) => (
136
+ <SelectInput<PropertyType>
137
+ {...props}
138
+ options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
139
+ value,
140
+ label: t(`format ${value}`),
141
+ }))}
142
+ />
143
+ ),
144
+ ['referenceSchema' satisfies keyof PropertyType]: (props) => (
145
+ <SelectInput<PropertyType>
146
+ {...props}
147
+ options={schemas.map((schema) => ({
148
+ value: schema.typename,
149
+ }))}
150
+ />
151
+ ),
152
+ ['referencePath' satisfies keyof PropertyType]: (props) => (
153
+ <SelectInput<PropertyType>
154
+ {...props}
155
+ options={
156
+ schema
157
+ ? getSchemaProperties(schema.schema.ast)
158
+ .sort(sortProperties)
159
+ .map((p) => ({ value: p.name }))
160
+ : []
161
+ }
162
+ />
163
+ ),
164
+ }}
165
+ />
166
+ );
167
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './FieldEditor';
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type LatLng } from '@dxos/echo-schema';
8
+ import { Input, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { InputHeader, type InputProps } from './Input';
11
+ import { translationKey } from '../../translations';
12
+
13
+ //
14
+ // Custom format components.
15
+ //
16
+
17
+ export const LatLngInput = <T extends object>({
18
+ property,
19
+ type,
20
+ label,
21
+ disabled,
22
+ getStatus,
23
+ getValue,
24
+ onValueChange,
25
+ onBlur,
26
+ }: InputProps<T>) => {
27
+ const { t } = useTranslation(translationKey);
28
+ const { status, error } = getStatus?.(property);
29
+ const { lat = 0, lng = 0 } = getValue<LatLng>(property) ?? {};
30
+
31
+ return (
32
+ <Input.Root validationValence={status}>
33
+ <InputHeader error={error}>
34
+ <Input.Label>{label}</Input.Label>
35
+ </InputHeader>
36
+ <div className='grid grid-cols-2 gap-2'>
37
+ <Input.TextInput
38
+ type='number'
39
+ disabled={disabled}
40
+ placeholder={t('placeholder latitude')}
41
+ value={lat}
42
+ onChange={(event) => onValueChange(property, type, { lat: safeParseFloat(event.target.value, 0), lng })}
43
+ onBlur={onBlur}
44
+ />
45
+ <Input.TextInput
46
+ type='number'
47
+ disabled={disabled}
48
+ placeholder={t('placeholder longitude')}
49
+ value={lng}
50
+ onChange={(event) => onValueChange(property, type, { lat, lng: safeParseFloat(event.target.value, 0) })}
51
+ onBlur={onBlur}
52
+ />
53
+ </div>
54
+ </Input.Root>
55
+ );
56
+ };
57
+
58
+ // TODO(burdon): Factor out.
59
+ const safeParseFloat = (str: string, defaultValue?: number): number | undefined => {
60
+ try {
61
+ return parseFloat(str);
62
+ } catch {
63
+ return defaultValue ?? 0;
64
+ }
65
+ };