@dxos/react-ui-form 0.8.4-main.422d1c7879 → 0.8.4-main.4f23b4e393

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 (110) hide show
  1. package/dist/lib/browser/index.mjs +148 -273
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/translations.mjs +82 -0
  5. package/dist/lib/browser/translations.mjs.map +7 -0
  6. package/dist/lib/node-esm/index.mjs +148 -273
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/lib/node-esm/translations.mjs +84 -0
  10. package/dist/lib/node-esm/translations.mjs.map +7 -0
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -63
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.d.ts +19 -3
  15. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  16. package/dist/types/src/components/Form/Form.stories.d.ts +72 -72
  17. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  18. package/dist/types/src/components/Form/FormField.d.ts +4 -4
  19. package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormFieldComponent.d.ts +5 -6
  21. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/FormFieldSet.d.ts +4 -7
  24. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
  26. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +64 -63
  31. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -1
  33. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
  34. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
  35. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +63 -63
  37. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
  39. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
  40. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
  41. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
  42. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +1 -1
  43. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  44. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -63
  45. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  46. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +1 -1
  47. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
  48. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +63 -63
  49. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/Settings/Settings.d.ts +10 -2
  51. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
  52. package/dist/types/src/components/Settings/Settings.stories.d.ts +63 -63
  53. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +15 -10
  55. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -1
  56. package/dist/types/src/components/Settings/SettingsItem.d.ts +8 -9
  57. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -1
  58. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
  59. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
  60. package/dist/types/src/components/Settings/index.d.ts +1 -0
  61. package/dist/types/src/components/Settings/index.d.ts.map +1 -1
  62. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -4
  63. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  64. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +63 -63
  65. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  66. package/dist/types/src/components/testing.d.ts.map +1 -1
  67. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  68. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
  69. package/dist/types/src/index.d.ts +0 -2
  70. package/dist/types/src/index.d.ts.map +1 -1
  71. package/dist/types/src/translations.d.ts +63 -63
  72. package/dist/types/src/translations.d.ts.map +1 -1
  73. package/dist/types/src/util/field-type.d.ts +1 -1
  74. package/dist/types/src/util/field-type.d.ts.map +1 -1
  75. package/dist/types/src/util/field.d.ts.map +1 -1
  76. package/dist/types/src/util/formatting.d.ts.map +1 -1
  77. package/dist/types/src/util/properties.d.ts.map +1 -1
  78. package/dist/types/src/util/refs.d.ts.map +1 -1
  79. package/dist/types/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +39 -30
  81. package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -1
  82. package/src/components/FieldEditor/FieldEditor.tsx +3 -2
  83. package/src/components/Form/Form.stories.tsx +2 -1
  84. package/src/components/Form/Form.tsx +3 -3
  85. package/src/components/Form/FormField.tsx +2 -1
  86. package/src/components/Form/FormFieldComponent.tsx +3 -3
  87. package/src/components/Form/fields/ArrayField.tsx +6 -9
  88. package/src/components/Form/fields/GeoPointField.tsx +3 -2
  89. package/src/components/Form/fields/MarkdownField.stories.tsx +56 -1
  90. package/src/components/Form/fields/MarkdownField.tsx +2 -1
  91. package/src/components/Form/fields/RefField.tsx +4 -3
  92. package/src/components/Form/fields/SelectOptionField.stories.tsx +2 -1
  93. package/src/components/Form/fields/SelectOptionField.tsx +6 -5
  94. package/src/components/ObjectForm/ObjectForm.tsx +6 -5
  95. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +8 -5
  96. package/src/components/ObjectPicker/ObjectPicker.tsx +18 -9
  97. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +2 -1
  98. package/src/components/ObjectProperties/ObjectProperties.tsx +5 -4
  99. package/src/components/Settings/AUDIT.md +52 -0
  100. package/src/components/Settings/Settings.stories.tsx +2 -1
  101. package/src/components/Settings/Settings.tsx +3 -4
  102. package/src/components/Settings/SettingsFieldSet.tsx +55 -9
  103. package/src/components/Settings/SettingsItem.tsx +4 -8
  104. package/src/components/Settings/SettingsSelectField.tsx +59 -0
  105. package/src/components/Settings/index.ts +1 -0
  106. package/src/components/ViewEditor/ViewEditor.stories.tsx +7 -7
  107. package/src/components/ViewEditor/ViewEditor.tsx +17 -24
  108. package/src/components/testing.tsx +1 -1
  109. package/src/index.ts +0 -4
  110. package/src/util/field-type.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.8.4-main.422d1c7879",
3
+ "version": "0.8.4-main.4f23b4e393",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -11,12 +11,21 @@
11
11
  "license": "MIT",
12
12
  "author": "DXOS.org",
13
13
  "type": "module",
14
+ "imports": {
15
+ "#translations": "./src/translations.ts"
16
+ },
14
17
  "exports": {
15
18
  ".": {
16
19
  "source": "./src/index.ts",
17
20
  "types": "./dist/types/src/index.d.ts",
18
21
  "browser": "./dist/lib/browser/index.mjs",
19
22
  "node": "./dist/lib/node-esm/index.mjs"
23
+ },
24
+ "./translations": {
25
+ "source": "./src/translations.ts",
26
+ "types": "./dist/types/src/translations.d.ts",
27
+ "browser": "./dist/lib/browser/translations.mjs",
28
+ "node": "./dist/lib/node-esm/translations.mjs"
20
29
  }
21
30
  },
22
31
  "types": "dist/types/src/index.d.ts",
@@ -31,26 +40,26 @@
31
40
  "@radix-ui/react-use-controllable-state": "1.1.0",
32
41
  "date-fns": "^3.6.0",
33
42
  "tabster": "^8.5.5",
34
- "@dxos/async": "0.8.4-main.422d1c7879",
35
- "@dxos/echo": "0.8.4-main.422d1c7879",
36
- "@dxos/echo-atom": "0.8.4-main.422d1c7879",
37
- "@dxos/echo-react": "0.8.4-main.422d1c7879",
38
- "@dxos/invariant": "0.8.4-main.422d1c7879",
39
- "@dxos/effect": "0.8.4-main.422d1c7879",
40
- "@dxos/keys": "0.8.4-main.422d1c7879",
41
- "@dxos/echo-db": "0.8.4-main.422d1c7879",
42
- "@dxos/lit-ui": "0.8.4-main.422d1c7879",
43
- "@dxos/log": "0.8.4-main.422d1c7879",
44
- "@dxos/react-ui-components": "0.8.4-main.422d1c7879",
45
- "@dxos/react-client": "0.8.4-main.422d1c7879",
46
- "@dxos/react-ui-editor": "0.8.4-main.422d1c7879",
47
- "@dxos/react-ui-list": "0.8.4-main.422d1c7879",
48
- "@dxos/react-ui-search": "0.8.4-main.422d1c7879",
49
- "@dxos/schema": "0.8.4-main.422d1c7879",
50
- "@dxos/ui-editor": "0.8.4-main.422d1c7879",
51
- "@dxos/util": "0.8.4-main.422d1c7879",
52
- "@dxos/types": "0.8.4-main.422d1c7879",
53
- "@dxos/react-ui-pickers": "0.8.4-main.422d1c7879"
43
+ "@dxos/echo": "0.8.4-main.4f23b4e393",
44
+ "@dxos/async": "0.8.4-main.4f23b4e393",
45
+ "@dxos/echo-react": "0.8.4-main.4f23b4e393",
46
+ "@dxos/effect": "0.8.4-main.4f23b4e393",
47
+ "@dxos/echo-atom": "0.8.4-main.4f23b4e393",
48
+ "@dxos/echo-db": "0.8.4-main.4f23b4e393",
49
+ "@dxos/invariant": "0.8.4-main.4f23b4e393",
50
+ "@dxos/keys": "0.8.4-main.4f23b4e393",
51
+ "@dxos/react-client": "0.8.4-main.4f23b4e393",
52
+ "@dxos/lit-ui": "0.8.4-main.4f23b4e393",
53
+ "@dxos/react-ui-components": "0.8.4-main.4f23b4e393",
54
+ "@dxos/react-ui-editor": "0.8.4-main.4f23b4e393",
55
+ "@dxos/react-ui-list": "0.8.4-main.4f23b4e393",
56
+ "@dxos/react-ui-pickers": "0.8.4-main.4f23b4e393",
57
+ "@dxos/react-ui-search": "0.8.4-main.4f23b4e393",
58
+ "@dxos/schema": "0.8.4-main.4f23b4e393",
59
+ "@dxos/types": "0.8.4-main.4f23b4e393",
60
+ "@dxos/ui-editor": "0.8.4-main.4f23b4e393",
61
+ "@dxos/log": "0.8.4-main.4f23b4e393",
62
+ "@dxos/util": "0.8.4-main.4f23b4e393"
54
63
  },
55
64
  "devDependencies": {
56
65
  "@types/react": "~19.2.7",
@@ -59,20 +68,20 @@
59
68
  "fast-check": "^3.19.0",
60
69
  "react": "~19.2.3",
61
70
  "react-dom": "~19.2.3",
62
- "vite": "^7.1.11",
63
- "@dxos/random": "0.8.4-main.422d1c7879",
64
- "@dxos/react-client": "0.8.4-main.422d1c7879",
65
- "@dxos/react-ui": "0.8.4-main.422d1c7879",
66
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.422d1c7879",
67
- "@dxos/ui-theme": "0.8.4-main.422d1c7879",
68
- "@dxos/storybook-utils": "0.8.4-main.422d1c7879"
71
+ "vite": "^8.0.10",
72
+ "@dxos/random": "0.8.4-main.4f23b4e393",
73
+ "@dxos/react-client": "0.8.4-main.4f23b4e393",
74
+ "@dxos/react-ui": "0.8.4-main.4f23b4e393",
75
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.4f23b4e393",
76
+ "@dxos/ui-theme": "0.8.4-main.4f23b4e393",
77
+ "@dxos/storybook-utils": "0.8.4-main.4f23b4e393"
69
78
  },
70
79
  "peerDependencies": {
71
80
  "effect": "3.20.0",
72
81
  "react": "~19.2.3",
73
82
  "react-dom": "~19.2.3",
74
- "@dxos/react-ui": "0.8.4-main.422d1c7879",
75
- "@dxos/ui-theme": "0.8.4-main.422d1c7879"
83
+ "@dxos/ui-theme": "0.8.4-main.4f23b4e393",
84
+ "@dxos/react-ui": "0.8.4-main.4f23b4e393"
76
85
  },
77
86
  "publishConfig": {
78
87
  "access": "public"
@@ -12,7 +12,8 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { ProjectionModel, ViewModel, createEchoChangeCallback } from '@dxos/schema';
13
13
  import { Example } from '@dxos/schema/testing';
14
14
 
15
- import { translations } from '../../translations';
15
+ import { translations } from '#translations';
16
+
16
17
  import { FIELD_EDITOR_DEBUG_SYMBOL, TestLayout } from '../testing';
17
18
  import { FieldEditor, type FieldEditorProps } from './FieldEditor';
18
19
 
@@ -16,7 +16,8 @@ import {
16
16
  getFormatSchema,
17
17
  } from '@dxos/schema';
18
18
 
19
- import { translationKey } from '../../translations';
19
+ import { translationKey } from '#translations';
20
+
20
21
  import { getFormProperties } from '../../util';
21
22
  import { Form, type FormFieldMap, type FormRootProps, SelectField, SelectOptionField } from '../Form';
22
23
 
@@ -151,7 +152,7 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
151
152
  const handleSave = useCallback<NonNullable<FormRootProps<PropertyType>['onSave']>>(
152
153
  (props) => {
153
154
  if (view) {
154
- Obj.change(view, () => {
155
+ Obj.update(view, () => {
155
156
  projection.setFieldProjection({ field, props });
156
157
  });
157
158
  } else {
@@ -14,7 +14,8 @@ import { withClientProvider } from '@dxos/react-client/testing';
14
14
  import { Tooltip } from '@dxos/react-ui';
15
15
  import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
16
16
 
17
- import { translations } from '../../translations';
17
+ import { translations } from '#translations';
18
+
18
19
  import { TestLayout } from '../testing';
19
20
  import { type ExcludeId, Form, type FormRootProps, omitId } from './Form';
20
21
 
@@ -21,6 +21,8 @@ import {
21
21
  } from '@dxos/react-ui';
22
22
  import { composable, composableProps, mx, withColumn } from '@dxos/ui-theme';
23
23
 
24
+ import { translationKey } from '#translations';
25
+
24
26
  import {
25
27
  type FormHandler,
26
28
  type FormHandlerProps,
@@ -28,7 +30,6 @@ import {
28
30
  useFormHandler,
29
31
  useKeyHandler,
30
32
  } from '../../hooks';
31
- import { translationKey } from '../../translations';
32
33
  import { FormFieldLabel, type FormFieldLabelProps, type FormFieldStateProps } from './FormFieldComponent';
33
34
  import {
34
35
  FormFieldSet as NaturalFormFieldSet,
@@ -265,7 +266,6 @@ const FormActions = ({ classNames }: FormActionsProps) => {
265
266
 
266
267
  return (
267
268
  <div
268
- role='none'
269
269
  className={mx(withColumn.center(), 'grid grid-flow-col gap-form-gap auto-cols-fr py-form-padding', classNames)}
270
270
  >
271
271
  {onCancel && (
@@ -341,7 +341,7 @@ const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
341
341
  }
342
342
 
343
343
  return (
344
- <div role='none' className={mx('flex w-full pt-form-padding', classNames)}>
344
+ <div className={mx('flex w-full pt-form-padding', classNames)}>
345
345
  <IconButton
346
346
  classNames='w-full'
347
347
  type='submit'
@@ -23,7 +23,8 @@ import {
23
23
  import { useTranslation } from '@dxos/react-ui';
24
24
  import { type ProjectionModel } from '@dxos/schema';
25
25
 
26
- import { translationKey } from '../../translations';
26
+ import { translationKey } from '#translations';
27
+
27
28
  import { getRefProps } from '../../util';
28
29
  import {
29
30
  ArrayField,
@@ -80,7 +80,7 @@ export type FormFieldLabelProps = {
80
80
  export const FormFieldLabel = ({ label, error, readonly, asChild }: FormFieldLabelProps) => {
81
81
  const Label = readonly || asChild ? 'span' : Input.Label;
82
82
  return (
83
- <div role='none' className='flex items-center justify-between'>
83
+ <div className='flex items-center justify-between'>
84
84
  <Label className={inputTextLabel}>{label}</Label>
85
85
  {error && (
86
86
  <Tooltip.Trigger asChild content={error} side='bottom'>
@@ -116,7 +116,7 @@ export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
116
116
  const str = String(value ?? '');
117
117
 
118
118
  return (
119
- <div role='none' className='contents'>
119
+ <div className='contents'>
120
120
  <Input.Root validationValence={status}>
121
121
  {layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
122
122
  {layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
@@ -159,7 +159,7 @@ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProp
159
159
  if (this.state.error) {
160
160
  return (
161
161
  <div className='flex gap-2 border border-rose-fill font-mono text-sm'>
162
- <span className='bg-rose-fill text-surface-text px-1 font-thin'>ERROR</span>
162
+ <span className='bg-rose-fill text-base-foreground px-1 font-thin'>ERROR</span>
163
163
  {String(this.props.path?.join('.'))}
164
164
  </div>
165
165
  );
@@ -9,7 +9,8 @@ import React, { useCallback } from 'react';
9
9
  import { findNode, getArrayElementType, getDiscriminatedType, isDiscriminatedUnion, isNestedType } from '@dxos/effect';
10
10
  import { IconButton, useTranslation } from '@dxos/react-ui';
11
11
 
12
- import { translationKey } from '../../../translations';
12
+ import { translationKey } from '#translations';
13
+
13
14
  import { getFormProperties } from '../../../util';
14
15
  import { useFormValues } from '../Form';
15
16
  import { FormField, type FormFieldProps } from '../FormField';
@@ -77,14 +78,10 @@ export const ArrayField = ({
77
78
  <FormFieldLabel readonly={readonly} label={label} asChild />
78
79
  )}
79
80
 
80
- <div role='none' className='flex flex-col gap-form-gap'>
81
+ <div className='flex flex-col gap-form-gap'>
81
82
  {values?.map((_, index) => {
82
83
  return (
83
- <div
84
- role='none'
85
- key={index}
86
- className='grid grid-cols-[1fr_min-content] gap-form-gap last:mb-form-gap items-center'
87
- >
84
+ <div key={index} className='grid grid-cols-[1fr_min-content] gap-form-gap last:mb-form-gap items-center'>
88
85
  <FormField
89
86
  autoFocus={index === values.length - 1}
90
87
  type={elementType}
@@ -95,9 +92,9 @@ export const ArrayField = ({
95
92
  />
96
93
 
97
94
  {!readonly && layout !== 'static' && (
98
- <div role='none' className='flex flex-col h-full justify-end'>
95
+ <div className='flex flex-col h-full justify-end'>
99
96
  {/* NOTE: Aligns with center of last field if multi-field object. */}
100
- <div role='none' className='flex items-center h-[2rem]'>
97
+ <div className='flex items-center h-[2rem]'>
101
98
  <IconButton
102
99
  icon='ph--x--regular'
103
100
  iconOnly
@@ -8,7 +8,8 @@ import { GeoLocation, type GeoPoint } from '@dxos/echo/internal';
8
8
  import { Input, useTranslation } from '@dxos/react-ui';
9
9
  import { safeParseFloat } from '@dxos/util';
10
10
 
11
- import { translationKey } from '../../../translations';
11
+ import { translationKey } from '#translations';
12
+
12
13
  import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
13
14
 
14
15
  export const GeoPointField = ({
@@ -62,7 +63,7 @@ export const GeoPointField = ({
62
63
  {layout === 'static' ? (
63
64
  <LatLng {...value} />
64
65
  ) : (
65
- <div role='none' className='grid grid-cols-2 gap-form-gap'>
66
+ <div className='grid grid-cols-2 gap-form-gap'>
66
67
  <div>
67
68
  <Input.Root>
68
69
  {layout !== 'inline' && <Input.Label>{t('latitude.label')}</Input.Label>}
@@ -5,6 +5,7 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useMemo } from 'react';
8
+ import { expect, userEvent } from 'storybook/test';
8
9
 
9
10
  import { Filter, Format, Obj, Ref } from '@dxos/echo';
10
11
  import { useQuery } from '@dxos/react-client/echo';
@@ -13,7 +14,8 @@ import { Panel } from '@dxos/react-ui';
13
14
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
14
15
  import { Text } from '@dxos/schema';
15
16
 
16
- import { translations } from '../../../translations';
17
+ import { translations } from '#translations';
18
+
17
19
  import { TestLayout } from '../../testing';
18
20
  import { Form } from '../Form';
19
21
 
@@ -125,6 +127,59 @@ export const StringBacked: Story = {
125
127
  render: () => <StringStory />,
126
128
  };
127
129
 
130
+ // Regression test: keystrokes into a string-backed markdown field must not
131
+ // blur the editor. Previously the EditorView was destroyed and recreated on
132
+ // every change because `onChange` was a useTextEditor dep and form callbacks
133
+ // are unstable across renders.
134
+ export const StringBackedKeepsFocus: Story = {
135
+ render: () => (
136
+ <TestLayout>
137
+ <Form.Root schema={StringSchema} defaultValues={{ notes: '' }}>
138
+ <Form.Viewport>
139
+ <Form.Content>
140
+ <Form.FieldSet />
141
+ </Form.Content>
142
+ </Form.Viewport>
143
+ </Form.Root>
144
+ </TestLayout>
145
+ ),
146
+ play: async ({ canvasElement }) => {
147
+ const waitFor = async <T,>(predicate: () => T | null | undefined, message: string): Promise<T> => {
148
+ for (let attempt = 0; attempt < 50; attempt++) {
149
+ const value = predicate();
150
+ if (value) {
151
+ return value;
152
+ }
153
+ await new Promise((resolve) => setTimeout(resolve, 100));
154
+ }
155
+ throw new Error(message);
156
+ };
157
+
158
+ const editorRoot = await waitFor(
159
+ () => canvasElement.querySelector<HTMLElement>('.cm-editor'),
160
+ 'CodeMirror editor root (.cm-editor) not found',
161
+ );
162
+ const editorContent = editorRoot.querySelector<HTMLElement>('.cm-content')!;
163
+ await expect(editorContent).toBeVisible();
164
+
165
+ // Focus editor and wait for focus to settle inside the editor root.
166
+ editorContent.focus();
167
+ await waitFor(() => (editorRoot.contains(document.activeElement) ? true : null), 'Editor did not receive focus');
168
+
169
+ // Type several characters; focus must be preserved inside the editor root
170
+ // after every keystroke. Regression: previously the EditorView was
171
+ // destroyed and recreated on every change, which detached the focused
172
+ // contentDOM and blurred the input.
173
+ const phrase = 'hello world';
174
+ for (const ch of phrase) {
175
+ await userEvent.keyboard(ch);
176
+ await expect(editorRoot.contains(document.activeElement)).toBe(true);
177
+ }
178
+
179
+ await expect(editorContent).toHaveTextContent(phrase);
180
+ },
181
+ };
182
+
128
183
  export const RefPopulated: Story = {
129
184
  render: () => <RefStory />,
130
185
  decorators: [
@@ -13,7 +13,8 @@ import { Editor, useBasicMarkdownExtensions } from '@dxos/react-ui-editor';
13
13
  import { Text } from '@dxos/schema';
14
14
  import { createDataExtensions } from '@dxos/ui-editor';
15
15
 
16
- import { translationKey } from '../../../translations';
16
+ import { translationKey } from '#translations';
17
+
17
18
  import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
18
19
 
19
20
  const editorClassNames =
@@ -14,7 +14,8 @@ import { DxAnchor } from '@dxos/lit-ui/react';
14
14
  import { Button, Icon, Input, useTranslation } from '@dxos/react-ui';
15
15
  import { ParentLabelAnnotationId } from '@dxos/schema';
16
16
 
17
- import { translationKey } from '../../../translations';
17
+ import { translationKey } from '#translations';
18
+
18
19
  import { ObjectPicker, type ObjectPickerContentProps, type RefOption } from '../../ObjectPicker';
19
20
  import { omitHiddenFormFields, omitId } from '../Form';
20
21
  import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
@@ -188,14 +189,14 @@ export const RefField = (props: RefFieldProps) => {
188
189
  <ObjectPicker.Root open={open} onOpenChange={setOpen}>
189
190
  <ObjectPicker.Trigger asChild classNames='p-0'>
190
191
  {item ? (
191
- <div role='none' className='flex gap-form-gap w-full'>
192
+ <div className='flex gap-form-gap w-full'>
192
193
  <Input.Root key={item.id}>
193
194
  <Input.TextInput value={item.label} readOnly classNames='w-full' />
194
195
  </Input.Root>
195
196
  </div>
196
197
  ) : (
197
198
  <Button classNames='w-full text-start gap-form-gap'>
198
- <div role='none' className='grow overflow-hidden'>
199
+ <div className='grow overflow-hidden'>
199
200
  <span className='truncate text-description'>{placeholder ?? t('ref-field.placeholder')}</span>
200
201
  </div>
201
202
  <Icon size={3} icon='ph--caret-down--bold' />
@@ -9,7 +9,8 @@ import React, { useState } from 'react';
9
9
  import { SelectOption } from '@dxos/echo/internal';
10
10
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
 
12
- import { translations } from '../../../translations';
12
+ import { translations } from '#translations';
13
+
13
14
  import { TestLayout } from '../../testing';
14
15
  import { Form } from '../Form';
15
16
  import { SelectOptionField } from './SelectOptionField';
@@ -9,9 +9,10 @@ import { PublicKey } from '@dxos/keys';
9
9
  import { type ChromaticPalette, IconButton, Input, Tag, useTranslation } from '@dxos/react-ui';
10
10
  import { List } from '@dxos/react-ui-list';
11
11
  import { HuePicker } from '@dxos/react-ui-pickers';
12
- import { hues, osTranslations, subtleHover } from '@dxos/ui-theme';
12
+ import { hues, osTranslations } from '@dxos/ui-theme';
13
+
14
+ import { translationKey } from '#translations';
13
15
 
14
- import { translationKey } from '../../../translations';
15
16
  import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
16
17
 
17
18
  export const SelectOptionField = ({
@@ -119,7 +120,7 @@ export const SelectOptionField = ({
119
120
  return (
120
121
  <Input.Root validationValence={status}>
121
122
  <FormFieldLabel error={error} readonly={readonly} label={label} />
122
- <div role='none'>
123
+ <div>
123
124
  {options && (
124
125
  <List.Root
125
126
  items={options}
@@ -134,7 +135,7 @@ export const SelectOptionField = ({
134
135
  <List.Item
135
136
  role='button'
136
137
  item={item}
137
- classNames={[subtleHover, 'flex flex-col cursor-pointer rounded-xs']}
138
+ classNames='flex flex-col cursor-pointer rounded-xs dx-hover'
138
139
  aria-expanded={selected === item.id}
139
140
  >
140
141
  <div className='flex items-center'>
@@ -146,7 +147,7 @@ export const SelectOptionField = ({
146
147
  <IconButton
147
148
  iconOnly
148
149
  variant='ghost'
149
- label={t(selected === item.id ? 'collapse label' : 'expand label', { ns: osTranslations })}
150
+ label={t(selected === item.id ? 'collapse.label' : 'expand.label', { ns: osTranslations })}
150
151
  icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
151
152
  onClick={() => handleClick(item.id)}
152
153
  />
@@ -11,7 +11,8 @@ import { invariant } from '@dxos/invariant';
11
11
  import { HuePicker } from '@dxos/react-ui-pickers';
12
12
  import { isNonNullable } from '@dxos/util';
13
13
 
14
- import { translationKey } from '../../translations';
14
+ import { translationKey } from '#translations';
15
+
15
16
  import { Form, type FormFieldMap, omitId } from '../Form';
16
17
 
17
18
  export type ObjectFormProps = {
@@ -52,7 +53,7 @@ export const ObjectForm = ({ object, schema }: ObjectFormProps) => {
52
53
  invariant(Type.isObjectSchema(schema));
53
54
  const newObject = db.add(Obj.make(schema, values));
54
55
  if (Obj.instanceOf(Tag.Tag, newObject)) {
55
- Obj.change(object, (object) => {
56
+ Obj.update(object, (object) => {
56
57
  Obj.getMeta(object).tags = [...(Obj.getMeta(object).tags ?? []), Obj.getDXN(newObject).toString()];
57
58
  });
58
59
  }
@@ -67,10 +68,10 @@ export const ObjectForm = ({ object, schema }: ObjectFormProps) => {
67
68
 
68
69
  const changedPaths = Object.keys(changed).filter((path) => changed[path as JsonPath]) as JsonPath[];
69
70
 
70
- // Handle tags separately using Obj.change.
71
+ // Handle tags separately using Obj.update.
71
72
  const hasTagsChange = changedPaths.some((path) => splitJsonPath(path)[0] === 'tags');
72
73
  if (hasTagsChange) {
73
- Obj.change(object, (object) => {
74
+ Obj.update(object, (object) => {
74
75
  Obj.getMeta(object).tags = tags?.map((tag: Ref.Ref<Tag.Tag>) => tag.dxn.toString()) ?? [];
75
76
  });
76
77
  }
@@ -78,7 +79,7 @@ export const ObjectForm = ({ object, schema }: ObjectFormProps) => {
78
79
  // Handle other property changes.
79
80
  const nonTagPaths = changedPaths.filter((path) => splitJsonPath(path)[0] !== 'tags');
80
81
  if (nonTagPaths.length > 0) {
81
- Obj.change(object, () => {
82
+ Obj.update(object, () => {
82
83
  for (const path of nonTagPaths) {
83
84
  const parts = splitJsonPath(path);
84
85
  const value = Obj.getValue(values, parts);
@@ -7,16 +7,17 @@ import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
8
8
  import { expect, fn, userEvent, within } from 'storybook/test';
9
9
 
10
- import { Obj } from '@dxos/echo';
10
+ import { Filter, Obj } from '@dxos/echo';
11
11
  import { random } from '@dxos/random';
12
- import { Filter, useQuery } from '@dxos/react-client/echo';
12
+ import { useQuery } from '@dxos/react-client/echo';
13
13
  import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
14
14
  import { Button } from '@dxos/react-ui';
15
15
  import { withTheme } from '@dxos/react-ui/testing';
16
16
  import { Person } from '@dxos/types';
17
17
  import { osTranslations } from '@dxos/ui-theme';
18
18
 
19
- import { translations } from '../../translations';
19
+ import { translations } from '#translations';
20
+
20
21
  import { ObjectPicker } from './ObjectPicker';
21
22
 
22
23
  random.seed(1);
@@ -64,7 +65,9 @@ const DefaultStory = () => {
64
65
  const handleCreateCallback = useCallback(
65
66
  (values: any) => {
66
67
  console.log('[on create]', values);
67
- if (!space) return;
68
+ if (!space) {
69
+ return;
70
+ }
68
71
  const newPerson = space.db.add(Obj.make(Person.Person, values));
69
72
  mockHandleCreate(values);
70
73
  return newPerson;
@@ -73,7 +76,7 @@ const DefaultStory = () => {
73
76
  );
74
77
 
75
78
  return (
76
- <div role='none' className='w-96'>
79
+ <div className='w-96'>
77
80
  <ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
78
81
  <ObjectPicker.Trigger asChild>
79
82
  <Button variant='primary' data-testid='trigger' classNames='w-full'>
@@ -6,9 +6,11 @@ import type * as Schema from 'effect/Schema';
6
6
  import React, { type KeyboardEvent, forwardRef, useCallback, useState } from 'react';
7
7
 
8
8
  import { type Palette, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
- import { Combobox, useSearchListInput, useSearchListResults } from '@dxos/react-ui-search';
9
+ import { Combobox } from '@dxos/react-ui-list';
10
+ import { useSearchListResults } from '@dxos/react-ui-search';
11
+
12
+ import { translationKey } from '#translations';
10
13
 
11
- import { translationKey } from '../../translations';
12
14
  import { Form } from '../Form';
13
15
  import { type FormFieldMap } from '../Form/FormFieldComponent';
14
16
 
@@ -57,7 +59,7 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
57
59
  const [showForm, setShowForm] = useState(false);
58
60
  const [formInitialValue, setFormInitialValue] = useState<string>('');
59
61
 
60
- const { results, handleSearch } = useSearchListResults({
62
+ const { results, query, handleSearch } = useSearchListResults({
61
63
  items: options,
62
64
  });
63
65
 
@@ -98,7 +100,7 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
98
100
 
99
101
  if (showForm && createSchema) {
100
102
  return (
101
- <Combobox.Content {...props} onSearch={handleSearch} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
103
+ <Combobox.Content {...props} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
102
104
  <Popover.Viewport>
103
105
  <Form.Root
104
106
  testId='create-referenced-object-form'
@@ -122,8 +124,13 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
122
124
  }
123
125
 
124
126
  return (
125
- <Combobox.Content {...props} onSearch={handleSearch} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
126
- <Combobox.Input placeholder={t('ref-field-combobox-input.placeholder')} autoFocus />
127
+ <Combobox.Content {...props} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
128
+ <Combobox.Input
129
+ placeholder={t('ref-field-combobox-input.placeholder')}
130
+ autoFocus
131
+ value={query}
132
+ onValueChange={handleSearch}
133
+ />
127
134
  <Combobox.List>
128
135
  {results.map((option) => (
129
136
  <Combobox.Item
@@ -137,10 +144,11 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
137
144
  ))}
138
145
  {createSchema && onCreate && (
139
146
  <CreateItem
147
+ query={query}
140
148
  createOptionLabel={createOptionLabel}
141
149
  createOptionIcon={createOptionIcon ?? 'ph--plus--regular'}
142
- onCreateItemSelect={(query: string) => {
143
- setFormInitialValue(query);
150
+ onCreateItemSelect={(currentQuery: string) => {
151
+ setFormInitialValue(currentQuery);
144
152
  setShowForm(true);
145
153
  }}
146
154
  />
@@ -153,16 +161,17 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
153
161
  );
154
162
 
155
163
  const CreateItem = ({
164
+ query,
156
165
  createOptionLabel,
157
166
  createOptionIcon,
158
167
  onCreateItemSelect,
159
168
  }: {
169
+ query: string;
160
170
  createOptionLabel?: [string, { ns: string }];
161
171
  createOptionIcon: string;
162
172
  onCreateItemSelect: (query: string) => void;
163
173
  }) => {
164
174
  const { t } = useTranslation(translationKey);
165
- const { query } = useSearchListInput();
166
175
 
167
176
  const label = createOptionLabel
168
177
  ? t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: query })
@@ -16,7 +16,8 @@ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
16
16
  import { FactoryAnnotation, type FactoryFn } from '@dxos/schema';
17
17
  import { Pipeline } from '@dxos/types';
18
18
 
19
- import { translations } from '../../translations';
19
+ import { translations } from '#translations';
20
+
20
21
  import { OBJECT_PROPERTIES_DEBUG_SYMBOL } from '../testing';
21
22
  import { ObjectProperties } from './ObjectProperties';
22
23