@flowgram.ai/form-materials 0.2.5 → 0.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowgram.ai/form-materials",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "homepage": "https://flowgram.ai/",
5
5
  "repository": "https://github.com/bytedance/flowgram.ai",
6
6
  "license": "MIT",
@@ -21,16 +21,16 @@
21
21
  "src"
22
22
  ],
23
23
  "dependencies": {
24
- "@douyinfe/semi-icons": "^2.72.3",
25
- "@douyinfe/semi-illustrations": "^2.36.0",
26
- "@douyinfe/semi-ui": "^2.72.3",
24
+ "@douyinfe/semi-icons": "^2.80.0",
25
+ "@douyinfe/semi-illustrations": "^2.80.0",
26
+ "@douyinfe/semi-ui": "^2.80.0",
27
27
  "lodash": "^4.17.21",
28
28
  "nanoid": "^4.0.2",
29
29
  "commander": "^11.0.0",
30
30
  "chalk": "^5.3.0",
31
31
  "inquirer": "^9.2.7",
32
32
  "immer": "~10.1.1",
33
- "@flowgram.ai/editor": "0.2.5"
33
+ "@flowgram.ai/editor": "0.2.7"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/lodash": "^4.14.137",
@@ -46,8 +46,8 @@
46
46
  "tsup": "^8.0.1",
47
47
  "typescript": "^5.0.4",
48
48
  "vitest": "^0.34.6",
49
- "@flowgram.ai/eslint-config": "0.2.5",
50
- "@flowgram.ai/ts-config": "0.2.5"
49
+ "@flowgram.ai/eslint-config": "0.2.7",
50
+ "@flowgram.ai/ts-config": "0.2.7"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": ">=16.8",
@@ -0,0 +1,130 @@
1
+ import React, { useRef, useState, useCallback } from 'react';
2
+
3
+ import { IconButton, JsonViewer, Tooltip } from '@douyinfe/semi-ui';
4
+ import { IconBrackets } from '@douyinfe/semi-icons';
5
+
6
+ import { getValueType } from './utils';
7
+ import {
8
+ ConstantInputWrapper,
9
+ JSONHeader,
10
+ JSONHeaderLeft,
11
+ JSONHeaderRight,
12
+ JSONViewerWrapper,
13
+ } from './styles';
14
+ import { ConstantInput } from '../constant-input';
15
+ import { IJsonSchema } from '../../typings';
16
+
17
+ /**
18
+ * 根据不同的数据类型渲染对应的默认值输入组件。
19
+ * @param props - 组件属性,包括 value, type, placeholder, onChange。
20
+ * @returns 返回对应类型的输入组件或 null。
21
+ */
22
+ export function DefaultValue(props: {
23
+ value: any;
24
+ schema?: IJsonSchema;
25
+ name?: string;
26
+ type?: string;
27
+ placeholder?: string;
28
+ jsonFormatText?: string;
29
+ onChange: (value: any) => void;
30
+ }) {
31
+ const { value, schema, type, onChange, placeholder, jsonFormatText } = props;
32
+
33
+ const wrapperRef = useRef<HTMLDivElement>(null);
34
+ const JsonViewerRef = useRef<JsonViewer>(null);
35
+
36
+ // 为 JsonViewer 添加状态管理
37
+ const [internalJsonValue, setInternalJsonValue] = useState<string>(
38
+ getValueType(value) === 'string' ? value : ''
39
+ );
40
+
41
+ // 使用 useCallback 创建稳定的回调函数
42
+ const handleJsonChange = useCallback((val: string) => {
43
+ // 只在值真正改变时才更新状态
44
+ if (val !== internalJsonValue) {
45
+ setInternalJsonValue(val);
46
+ }
47
+ }, []);
48
+
49
+ // 处理编辑完成事件
50
+ const handleEditComplete = useCallback(() => {
51
+ // 只有当存在key,编辑完成时才触发父组件的 onChange
52
+ onChange(internalJsonValue);
53
+ // 确保在更新后移除焦点
54
+ requestAnimationFrame(() => {
55
+ // JsonViewerRef.current?.format();
56
+ wrapperRef.current?.blur();
57
+ });
58
+ setJsonReadOnly(true);
59
+ }, [internalJsonValue, onChange]);
60
+
61
+ const [jsonReadOnly, setJsonReadOnly] = useState<boolean>(true);
62
+
63
+ const handleFormatJson = useCallback(() => {
64
+ try {
65
+ const parsed = JSON.parse(internalJsonValue);
66
+ const formatted = JSON.stringify(parsed, null, 4);
67
+ setInternalJsonValue(formatted);
68
+ onChange(formatted);
69
+ } catch (error) {
70
+ console.error('Invalid JSON:', error);
71
+ }
72
+ }, [internalJsonValue, onChange]);
73
+
74
+ return type === 'object' ? (
75
+ <>
76
+ <JSONHeader>
77
+ <JSONHeaderLeft>json</JSONHeaderLeft>
78
+ <JSONHeaderRight>
79
+ <Tooltip content={jsonFormatText ?? 'Format'}>
80
+ <IconButton
81
+ icon={<IconBrackets style={{ color: 'var(--semi-color-primary)' }} />}
82
+ size="small"
83
+ type="tertiary"
84
+ theme="borderless"
85
+ onClick={handleFormatJson}
86
+ />
87
+ </Tooltip>
88
+ </JSONHeaderRight>
89
+ </JSONHeader>
90
+
91
+ <JSONViewerWrapper
92
+ ref={wrapperRef}
93
+ tabIndex={-1}
94
+ onBlur={(e) => {
95
+ if (wrapperRef.current && !wrapperRef.current?.contains(e.relatedTarget as Node)) {
96
+ handleEditComplete();
97
+ }
98
+ }}
99
+ onClick={(e: React.MouseEvent) => {
100
+ setJsonReadOnly(false);
101
+ }}
102
+ >
103
+ <JsonViewer
104
+ ref={JsonViewerRef}
105
+ value={getValueType(value) === 'string' ? value : ''}
106
+ height={120}
107
+ width="100%"
108
+ showSearch={false}
109
+ options={{
110
+ readOnly: jsonReadOnly,
111
+ formatOptions: { tabSize: 4, insertSpaces: true, eol: '\n' },
112
+ }}
113
+ style={{
114
+ padding: 0,
115
+ }}
116
+ onChange={handleJsonChange}
117
+ />
118
+ </JSONViewerWrapper>
119
+ </>
120
+ ) : (
121
+ <ConstantInputWrapper>
122
+ <ConstantInput
123
+ value={value}
124
+ onChange={(_v) => onChange(_v)}
125
+ schema={schema || { type: 'string' }}
126
+ placeholder={placeholder ?? 'Default value if parameter is not provided'}
127
+ />
128
+ </ConstantInputWrapper>
129
+ );
130
+ }
@@ -29,8 +29,9 @@ import {
29
29
  UIType,
30
30
  } from './styles';
31
31
  import { UIName } from './styles';
32
- import { UIRow } from './styles';
32
+ import { DefaultValueWrapper, UIRow } from './styles';
33
33
  import { usePropertiesEdit } from './hooks';
34
+ import { DefaultValue } from './default-value';
34
35
  import { BlurInput } from './components/blur-input';
35
36
 
36
37
  export function JsonSchemaEditor(props: {
@@ -47,11 +48,12 @@ export function JsonSchemaEditor(props: {
47
48
  return (
48
49
  <UIContainer>
49
50
  <UIProperties>
50
- {propertyList.map((_property) => (
51
+ {propertyList.map((_property, index) => (
51
52
  <PropertyEdit
52
53
  key={_property.key}
53
54
  value={_property}
54
55
  config={config}
56
+ $index={index}
55
57
  onChange={(_v) => {
56
58
  onEditProperty(_property.key!, _v);
57
59
  }}
@@ -74,14 +76,31 @@ function PropertyEdit(props: {
74
76
  onChange?: (value: PropertyValueType) => void;
75
77
  onRemove?: () => void;
76
78
  $isLast?: boolean;
79
+ $index?: number;
80
+ $isFirst?: boolean;
81
+ $parentExpand?: boolean;
82
+ $parentType?: string;
77
83
  $showLine?: boolean;
84
+ $level?: number; // 添加层级属性
78
85
  }) {
79
- const { value, config, onChange: onChangeProps, onRemove, $isLast, $showLine } = props;
86
+ const {
87
+ value,
88
+ config,
89
+ $level = 0,
90
+ onChange: onChangeProps,
91
+ onRemove,
92
+ $index,
93
+ $isFirst,
94
+ $isLast,
95
+ $parentExpand = false,
96
+ $parentType = '',
97
+ $showLine,
98
+ } = props;
80
99
 
81
100
  const [expand, setExpand] = useState(false);
82
101
  const [collapse, setCollapse] = useState(false);
83
102
 
84
- const { name, type, items, description, isPropertyRequired } = value || {};
103
+ const { name, type, items, default: defaultValue, description, isPropertyRequired } = value || {};
85
104
 
86
105
  const typeSelectorValue = useMemo(() => ({ type, items }), [type, items]);
87
106
 
@@ -99,7 +118,16 @@ function PropertyEdit(props: {
99
118
 
100
119
  return (
101
120
  <>
102
- <UIPropertyLeft $isLast={$isLast} $showLine={$showLine}>
121
+ <UIPropertyLeft
122
+ type={type}
123
+ $index={$index}
124
+ $isFirst={$isFirst}
125
+ $isLast={$isLast}
126
+ $showLine={$showLine}
127
+ $isExpand={expand}
128
+ $parentExpand={$parentExpand}
129
+ $parentType={$parentType}
130
+ >
103
131
  {showCollapse && (
104
132
  <UICollapseTrigger onClick={() => setCollapse((_collapse) => !_collapse)}>
105
133
  {collapse ? <IconChevronDown size="small" /> : <IconChevronRight size="small" />}
@@ -107,7 +135,12 @@ function PropertyEdit(props: {
107
135
  )}
108
136
  </UIPropertyLeft>
109
137
  <UIPropertyRight>
110
- <UIPropertyMain $expand={expand}>
138
+ <UIPropertyMain
139
+ $showCollapse={showCollapse}
140
+ $collapse={collapse}
141
+ $expand={expand}
142
+ type={type}
143
+ >
111
144
  <UIRow>
112
145
  <UIName>
113
146
  <BlurInput
@@ -139,7 +172,9 @@ function PropertyEdit(props: {
139
172
  size="small"
140
173
  theme="borderless"
141
174
  icon={expand ? <IconShrink size="small" /> : <IconExpand size="small" />}
142
- onClick={() => setExpand((_expand) => !_expand)}
175
+ onClick={() => {
176
+ setExpand((_expand) => !_expand);
177
+ }}
143
178
  />
144
179
  {isDrilldownObject && (
145
180
  <IconButton
@@ -169,6 +204,23 @@ function PropertyEdit(props: {
169
204
  onChange={(value) => onChange('description', value)}
170
205
  placeholder={config?.descPlaceholder ?? 'Help LLM to understand the property'}
171
206
  />
207
+ {$level === 0 && type && type !== 'array' && (
208
+ <>
209
+ <UILabel style={{ marginTop: 10 }}>
210
+ {config?.defaultValueTitle ?? 'Default Value'}
211
+ </UILabel>
212
+ <DefaultValueWrapper>
213
+ <DefaultValue
214
+ value={defaultValue}
215
+ schema={value}
216
+ type={type}
217
+ placeholder={config?.defaultValuePlaceholder}
218
+ jsonFormatText={config?.jsonFormatText}
219
+ onChange={(value) => onChange('default', value)}
220
+ />
221
+ </DefaultValueWrapper>
222
+ </>
223
+ )}
172
224
  </UIExpandDetail>
173
225
  )}
174
226
  </UIPropertyMain>
@@ -180,6 +232,9 @@ function PropertyEdit(props: {
180
232
  key={_property.key}
181
233
  value={_property}
182
234
  config={config}
235
+ $level={$level + 1} // 传递递增的层级
236
+ $parentExpand={expand}
237
+ $parentType={type}
183
238
  onChange={(_v) => {
184
239
  onEditProperty(_property.key!, _v);
185
240
  }}
@@ -187,6 +242,8 @@ function PropertyEdit(props: {
187
242
  onRemoveProperty(_property.key!);
188
243
  }}
189
244
  $isLast={index === propertyList.length - 1}
245
+ $isFirst={index === 0}
246
+ $index={index}
190
247
  $showLine={true}
191
248
  />
192
249
  ))}
@@ -46,37 +46,55 @@ export const UIProperties = styled.div<{ $shrink?: boolean }>`
46
46
  `}
47
47
  `;
48
48
 
49
- export const UIPropertyLeft = styled.div<{ $isLast?: boolean; $showLine?: boolean }>`
49
+ export const UIPropertyLeft = styled.div<{
50
+ $isLast?: boolean;
51
+ $showLine?: boolean;
52
+ $isExpand?: boolean;
53
+ type?: string;
54
+ $isFirst?: boolean;
55
+ $index?: number;
56
+ $parentExpand?: boolean;
57
+ $parentType?: string;
58
+ }>`
50
59
  grid-column: 1;
51
60
  position: relative;
52
-
53
- ${({ $showLine, $isLast }) =>
54
- $showLine &&
55
- css`
56
- &::before {
57
- /* 竖线 */
58
- content: '';
59
- position: absolute;
60
- left: -22px;
61
- top: -18px;
62
- bottom: ${$isLast ? '12px' : '0px'};
63
- width: 1px;
64
- background: #d9d9d9;
65
- display: block;
66
- }
67
-
68
- &::after {
69
- /* 横线 */
70
- content: '';
71
- position: absolute;
72
- left: -22px; // 横线起点和竖线对齐
73
- top: 12px; // 跟随你的行高调整
74
- width: 22px; // 横线长度
75
- height: 1px;
76
- background: #d9d9d9;
77
- display: block;
78
- }
79
- `}
61
+ width: 16px;
62
+
63
+ ${({ $showLine, $isLast, $parentType }) => {
64
+ let height = '100%';
65
+ if ($parentType && $isLast) {
66
+ height = '24px';
67
+ }
68
+
69
+ return (
70
+ $showLine &&
71
+ css`
72
+ &::before {
73
+ /* 竖线 */
74
+ content: '';
75
+ height: ${height};
76
+ position: absolute;
77
+ left: -22px;
78
+ top: -16px;
79
+ width: 1px;
80
+ background: #d9d9d9;
81
+ display: block;
82
+ }
83
+
84
+ &::after {
85
+ /* 横线 */
86
+ content: '';
87
+ position: absolute;
88
+ left: -22px; // 横线起点和竖线对齐
89
+ top: 8px; // 跟随你的行高调整
90
+ width: 18px; // 横线长度
91
+ height: 1px;
92
+ background: #d9d9d9;
93
+ display: block;
94
+ }
95
+ `
96
+ );
97
+ }}
80
98
  `;
81
99
 
82
100
  export const UIPropertyRight = styled.div`
@@ -88,18 +106,47 @@ export const UIPropertyRight = styled.div`
88
106
  }
89
107
  `;
90
108
 
91
- export const UIPropertyMain = styled.div<{ $expand?: boolean }>`
109
+ export const UIPropertyMain = styled.div<{
110
+ $expand?: boolean;
111
+ type?: string;
112
+ $collapse?: boolean;
113
+ $showCollapse?: boolean;
114
+ }>`
92
115
  display: flex;
93
116
  flex-direction: column;
94
117
  gap: 10px;
118
+ position: relative;
95
119
 
96
- ${({ $expand }) =>
97
- $expand &&
98
- css`
99
- background-color: #f5f5f5;
100
- padding: 10px;
101
- border-radius: 4px;
102
- `}
120
+ ${({ $expand, type, $collapse, $showCollapse }) => {
121
+ const beforeElement = `
122
+ &::before {
123
+ /* 竖线 */
124
+ content: '';
125
+ height: 100%;
126
+ position: absolute;
127
+ left: -12px;
128
+ top: 18px;
129
+ width: 1px;
130
+ background: #d9d9d9;
131
+ display: block;
132
+ }`;
133
+
134
+ return (
135
+ $expand &&
136
+ css`
137
+ background-color: #f5f5f5;
138
+ padding: 10px;
139
+ border-radius: 4px;
140
+
141
+ ${$showCollapse &&
142
+ $collapse &&
143
+ (type === 'array' || type === 'object') &&
144
+ css`
145
+ ${beforeElement}
146
+ `}
147
+ `
148
+ );
149
+ }}
103
150
  `;
104
151
 
105
152
  export const UICollapsible = styled.div<{ $collapse?: boolean }>`
@@ -143,3 +190,46 @@ const iconAddChildrenSvg = (
143
190
  );
144
191
 
145
192
  export const IconAddChildren = () => <Icon size="small" svg={iconAddChildrenSvg} />;
193
+
194
+ export const DefaultValueWrapper = styled.div`
195
+ margin: 0;
196
+ `;
197
+
198
+ export const JSONViewerWrapper = styled.div`
199
+ padding: 0 0 24px;
200
+ &:first-child {
201
+ margin-top: 0px;
202
+ }
203
+ `;
204
+
205
+ export const JSONHeader = styled.div`
206
+ display: flex;
207
+ justify-content: space-between;
208
+ align-items: center;
209
+ background-color: var(--semi-color-fill-0);
210
+ border-radius: 6px 6px 0 0;
211
+ height: 36px;
212
+ padding: 0 8px 0 12px;
213
+ `;
214
+
215
+ export const JSONHeaderLeft = styled.div`
216
+ display: flex;
217
+ align-items: center;
218
+ gap: 10px;
219
+ `;
220
+
221
+ export const JSONHeaderRight = styled.div`
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 10px;
225
+ `;
226
+
227
+ export const ConstantInputWrapper = styled.div`
228
+ flex-grow: 1;
229
+
230
+ & .semi-tree-select,
231
+ & .semi-input-number,
232
+ & .semi-select {
233
+ width: 100%;
234
+ }
235
+ `;
@@ -14,5 +14,8 @@ export interface ConfigType {
14
14
  placeholder?: string;
15
15
  descTitle?: string;
16
16
  descPlaceholder?: string;
17
+ defaultValueTitle?: string;
18
+ defaultValuePlaceholder?: string;
17
19
  addButtonText?: string;
20
+ jsonFormatText?: string;
18
21
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Return the corresponding string description according to the type of the input value.根据输入值的类型返回对应的字符串描述。
3
+ * @param value - 需要判断类型的值。The value whose type needs to be judged.
4
+ * @returns 返回值的类型字符串 The type string of the return value('string', 'integer', 'number', 'boolean', 'object', 'array', 'other')。
5
+ */
6
+ export function getValueType(value: any): string {
7
+ const type = typeof value;
8
+
9
+ if (type === 'string') {
10
+ return 'string';
11
+ } else if (type === 'number') {
12
+ return Number.isInteger(value) ? 'integer' : 'number';
13
+ } else if (type === 'boolean') {
14
+ return 'boolean';
15
+ } else if (type === 'object') {
16
+ if (value === null) {
17
+ return 'other';
18
+ }
19
+ return Array.isArray(value) ? 'array' : 'object';
20
+ } else {
21
+ // undefined, function, symbol, bigint etc.
22
+ return 'other';
23
+ }
24
+ }
@@ -27,7 +27,7 @@ export type VariableSelectorProps = PropTypes;
27
27
 
28
28
  export const VariableSelector = ({
29
29
  value,
30
- config,
30
+ config = {},
31
31
  onChange,
32
32
  style,
33
33
  readonly = false,