@dxos/react-ui-form 0.8.2-main.36232bc → 0.8.2-main.5885341

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.
@@ -0,0 +1,152 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useEffect, useState } from 'react';
6
+
7
+ import {
8
+ type AST,
9
+ Expando,
10
+ getTypeAnnotation,
11
+ Ref,
12
+ ReferenceAnnotationId,
13
+ type ReferenceAnnotationValue,
14
+ type TypeAnnotation,
15
+ } from '@dxos/echo-schema';
16
+ import { findAnnotation, type SimpleType } from '@dxos/effect';
17
+ import { DXN } from '@dxos/keys';
18
+ import { refFromDXN } from '@dxos/live-object';
19
+ import { log } from '@dxos/log';
20
+ import { type MaybePromise } from '@dxos/util';
21
+
22
+ import { SelectInput, TextInput } from './Defaults';
23
+ import { type FormInputStateProps } from './FormContext';
24
+
25
+ export type QueryRefOptions = (type: TypeAnnotation) => MaybePromise<{ dxn: DXN; label?: string }[]>;
26
+
27
+ type RefFieldProps = {
28
+ ast: AST.AST;
29
+ type: SimpleType;
30
+ label: string;
31
+ readonly?: boolean;
32
+ placeholder?: string;
33
+ inline?: boolean;
34
+ onQueryRefOptions?: QueryRefOptions;
35
+ inputProps: FormInputStateProps;
36
+ };
37
+
38
+ export const RefField = ({
39
+ ast,
40
+ type,
41
+ label,
42
+ readonly,
43
+ placeholder,
44
+ inline,
45
+ onQueryRefOptions,
46
+ inputProps,
47
+ }: RefFieldProps) => {
48
+ const { getValue, onValueChange, ...restInputProps } = inputProps;
49
+ const refTypeInfo = findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId);
50
+ const [refOptions, setRefOptions] = useState<Array<{ value: string; label?: string }>>([]);
51
+ const [loading, setLoading] = useState(false);
52
+
53
+ useEffect(() => {
54
+ if (!refTypeInfo || !onQueryRefOptions) {
55
+ return;
56
+ }
57
+
58
+ const fetchOptions = async () => {
59
+ setLoading(true);
60
+ try {
61
+ const options = await onQueryRefOptions(refTypeInfo);
62
+ setRefOptions(
63
+ options.map((option) => ({
64
+ label: option.label,
65
+ value: option.dxn.toString(),
66
+ })),
67
+ );
68
+ } catch (error) {
69
+ log.error('Failed to fetch ref options:', error);
70
+ setRefOptions([]);
71
+ } finally {
72
+ setLoading(false);
73
+ }
74
+ };
75
+
76
+ void fetchOptions();
77
+ }, [refTypeInfo, onQueryRefOptions]);
78
+
79
+ if (!refTypeInfo) {
80
+ return null;
81
+ }
82
+
83
+ // If ref type is expando, fall back to taking a DXN in string format.
84
+ if (refTypeInfo.typename === getTypeAnnotation(Expando)?.typename || !onQueryRefOptions) {
85
+ const handleOnValueChange = (_type: any, dxnString: string) => {
86
+ const dxn = DXN.tryParse(dxnString);
87
+ if (dxn) {
88
+ onValueChange?.('object', refFromDXN(dxn));
89
+ } else if (dxnString === '') {
90
+ onValueChange?.('object', undefined);
91
+ } else {
92
+ onValueChange?.('string', dxnString);
93
+ }
94
+ };
95
+
96
+ const handleGetValue = () => {
97
+ const formValue = getValue();
98
+ if (typeof formValue === 'string') {
99
+ return formValue;
100
+ }
101
+ if (Ref.isRef(formValue)) {
102
+ return formValue.dxn.toString();
103
+ }
104
+
105
+ return undefined;
106
+ };
107
+
108
+ return (
109
+ <TextInput
110
+ type={type}
111
+ label={label}
112
+ disabled={readonly}
113
+ placeholder={placeholder}
114
+ inputOnly={inline}
115
+ getValue={handleGetValue as <V>() => V | undefined}
116
+ onValueChange={handleOnValueChange}
117
+ {...restInputProps}
118
+ />
119
+ );
120
+ }
121
+
122
+ const handleGetValue = () => {
123
+ const formValue = getValue();
124
+
125
+ if (Ref.isRef(formValue)) {
126
+ return formValue.dxn.toString();
127
+ }
128
+
129
+ return undefined;
130
+ };
131
+
132
+ const handleValueChanged = (_type: any, dxnString: string) => {
133
+ const dxn = DXN.parse(dxnString);
134
+ const ref = refFromDXN(dxn);
135
+
136
+ onValueChange('object', ref);
137
+ };
138
+
139
+ return (
140
+ <SelectInput
141
+ type={type}
142
+ label={label}
143
+ disabled={readonly || loading}
144
+ placeholder={loading ? 'Loading options...' : placeholder}
145
+ inputOnly={inline}
146
+ getValue={handleGetValue as <V>() => V | undefined}
147
+ onValueChange={handleValueChanged}
148
+ options={refOptions}
149
+ {...restInputProps}
150
+ />
151
+ );
152
+ };
@@ -139,15 +139,16 @@ export const ViewEditor = ({
139
139
  );
140
140
 
141
141
  return (
142
- <div role='none' className={mx('flex flex-col w-full divide-y divide-separator', classNames)}>
142
+ <div role='none' className={mx('grid grid-rows-[1fr_1fr] min-bs-0', classNames)}>
143
143
  <Form<ViewMetaType>
144
144
  autoSave
145
145
  schema={ViewMetaSchema}
146
146
  values={viewValues}
147
147
  onSave={schema.readonly ? undefined : handleUpdate}
148
+ classNames='min-bs-0 overflow-y-auto'
148
149
  />
149
150
 
150
- <div>
151
+ <div role='none' className='border-bs border-separator min-bs-0 overflow-y-auto'>
151
152
  {/* TODO(burdon): Clean up common form ux. */}
152
153
  <div role='none' className='p-2'>
153
154
  <label className={mx(inputTextLabel)}>{t('fields label')}</label>