@flowgram.ai/form-materials 0.1.0-alpha.8

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 (60) hide show
  1. package/bin/index.ts +94 -0
  2. package/bin/materials.ts +137 -0
  3. package/bin/project.ts +86 -0
  4. package/dist/esm/index.js +1978 -0
  5. package/dist/esm/index.js.map +1 -0
  6. package/dist/index.d.mts +285 -0
  7. package/dist/index.d.ts +285 -0
  8. package/dist/index.js +2018 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +72 -0
  11. package/src/components/batch-variable-selector/config.json +5 -0
  12. package/src/components/batch-variable-selector/index.tsx +19 -0
  13. package/src/components/condition-row/config.json +5 -0
  14. package/src/components/condition-row/constants.ts +123 -0
  15. package/src/components/condition-row/hooks/useOp.tsx +45 -0
  16. package/src/components/condition-row/hooks/useRule.ts +26 -0
  17. package/src/components/condition-row/index.tsx +71 -0
  18. package/src/components/condition-row/styles.tsx +25 -0
  19. package/src/components/condition-row/types.ts +37 -0
  20. package/src/components/constant-input/config.json +6 -0
  21. package/src/components/constant-input/index.tsx +81 -0
  22. package/src/components/constant-input/types.ts +18 -0
  23. package/src/components/dynamic-value-input/config.json +5 -0
  24. package/src/components/dynamic-value-input/index.tsx +85 -0
  25. package/src/components/dynamic-value-input/styles.tsx +19 -0
  26. package/src/components/index.ts +7 -0
  27. package/src/components/json-schema-editor/components/blur-input.tsx +22 -0
  28. package/src/components/json-schema-editor/config.json +5 -0
  29. package/src/components/json-schema-editor/default-value.tsx +130 -0
  30. package/src/components/json-schema-editor/hooks.tsx +161 -0
  31. package/src/components/json-schema-editor/index.tsx +256 -0
  32. package/src/components/json-schema-editor/styles.tsx +235 -0
  33. package/src/components/json-schema-editor/types.ts +21 -0
  34. package/src/components/json-schema-editor/utils.ts +24 -0
  35. package/src/components/type-selector/config.json +5 -0
  36. package/src/components/type-selector/constants.tsx +359 -0
  37. package/src/components/type-selector/index.tsx +57 -0
  38. package/src/components/variable-selector/config.json +5 -0
  39. package/src/components/variable-selector/index.tsx +109 -0
  40. package/src/components/variable-selector/styles.tsx +43 -0
  41. package/src/components/variable-selector/use-variable-tree.tsx +101 -0
  42. package/src/effects/auto-rename-ref/config.json +5 -0
  43. package/src/effects/auto-rename-ref/index.ts +104 -0
  44. package/src/effects/index.ts +3 -0
  45. package/src/effects/provide-batch-input/config.json +5 -0
  46. package/src/effects/provide-batch-input/index.ts +38 -0
  47. package/src/effects/provide-batch-outputs/config.json +5 -0
  48. package/src/effects/provide-batch-outputs/index.ts +34 -0
  49. package/src/index.ts +4 -0
  50. package/src/typings/flow-value/config.json +5 -0
  51. package/src/typings/flow-value/index.ts +27 -0
  52. package/src/typings/index.ts +2 -0
  53. package/src/typings/json-schema/config.json +5 -0
  54. package/src/typings/json-schema/index.ts +31 -0
  55. package/src/utils/format-legacy-refs/config.json +5 -0
  56. package/src/utils/format-legacy-refs/index.ts +153 -0
  57. package/src/utils/format-legacy-refs/readme.md +38 -0
  58. package/src/utils/index.ts +2 -0
  59. package/src/utils/json-schema/config.json +5 -0
  60. package/src/utils/json-schema/index.ts +161 -0
@@ -0,0 +1,71 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import { Input } from '@douyinfe/semi-ui';
4
+
5
+ import { ConditionRowValueType, Op } from './types';
6
+ import { UIContainer, UILeft, UIOperator, UIRight, UIValues } from './styles';
7
+ import { useRule } from './hooks/useRule';
8
+ import { useOp } from './hooks/useOp';
9
+ import { VariableSelector } from '../variable-selector';
10
+ import { DynamicValueInput } from '../dynamic-value-input';
11
+ import { JsonSchemaBasicType } from '../../typings';
12
+
13
+ interface PropTypes {
14
+ value?: ConditionRowValueType;
15
+ onChange: (value?: ConditionRowValueType) => void;
16
+ style?: React.CSSProperties;
17
+ readonly?: boolean;
18
+ }
19
+
20
+ export function ConditionRow({ style, value, onChange, readonly }: PropTypes) {
21
+ const { left, operator, right } = value || {};
22
+ const { rule } = useRule(left);
23
+ const { renderOpSelect, opConfig } = useOp({
24
+ rule,
25
+ op: operator,
26
+ onChange: (v) => onChange({ ...value, operator: v }),
27
+ });
28
+
29
+ const targetSchema = useMemo(() => {
30
+ const targetType: JsonSchemaBasicType | null = rule?.[operator as Op] || null;
31
+ return targetType ? { type: targetType, extra: { weak: true } } : null;
32
+ }, [rule, opConfig]);
33
+
34
+ return (
35
+ <UIContainer style={style}>
36
+ <UIOperator>{renderOpSelect()}</UIOperator>
37
+ <UIValues>
38
+ <UILeft>
39
+ <VariableSelector
40
+ readonly={readonly}
41
+ style={{ width: '100%' }}
42
+ value={left?.content}
43
+ onChange={(v) =>
44
+ onChange({
45
+ ...value,
46
+ left: {
47
+ type: 'ref',
48
+ content: v,
49
+ },
50
+ })
51
+ }
52
+ />
53
+ </UILeft>
54
+ <UIRight>
55
+ {targetSchema ? (
56
+ <DynamicValueInput
57
+ readonly={readonly || !rule}
58
+ value={right}
59
+ schema={targetSchema}
60
+ onChange={(v) => onChange({ ...value, right: v })}
61
+ />
62
+ ) : (
63
+ <Input size="small" disabled value={opConfig?.rightDisplay || 'Empty'} />
64
+ )}
65
+ </UIRight>
66
+ </UIValues>
67
+ </UIContainer>
68
+ );
69
+ }
70
+
71
+ export { ConditionRowValueType };
@@ -0,0 +1,25 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const UIContainer = styled.div`
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 4px;
7
+ `;
8
+
9
+ export const UIOperator = styled.div``;
10
+
11
+ export const UILeft = styled.div`
12
+ width: 100%;
13
+ `;
14
+
15
+ export const UIRight = styled.div`
16
+ width: 100%;
17
+ `;
18
+
19
+ export const UIValues = styled.div`
20
+ flex-grow: 1;
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ gap: 4px;
25
+ `;
@@ -0,0 +1,37 @@
1
+ import { IFlowConstantRefValue, IFlowRefValue, JsonSchemaBasicType } from '../../typings';
2
+
3
+ export enum Op {
4
+ EQ = 'eq',
5
+ NEQ = 'neq',
6
+ GT = 'gt',
7
+ GTE = 'gte',
8
+ LT = 'lt',
9
+ LTE = 'lte',
10
+ IN = 'in',
11
+ NIN = 'nin',
12
+ CONTAINS = 'contains',
13
+ NOT_CONTAINS = 'not_contains',
14
+ IS_EMPTY = 'is_empty',
15
+ IS_NOT_EMPTY = 'is_not_empty',
16
+ IS_TRUE = 'is_true',
17
+ IS_FALSE = 'is_false',
18
+ }
19
+
20
+ export interface OpConfig {
21
+ label: string;
22
+ abbreviation: string;
23
+ // When right is not a value, display this text
24
+ rightDisplay?: string;
25
+ }
26
+
27
+ export type OpConfigs = Record<Op, OpConfig>;
28
+
29
+ export type IRule = Partial<Record<Op, JsonSchemaBasicType | null>>;
30
+
31
+ export type IRules = Record<JsonSchemaBasicType, IRule>;
32
+
33
+ export interface ConditionRowValueType {
34
+ left?: IFlowRefValue;
35
+ operator?: Op;
36
+ right?: IFlowConstantRefValue;
37
+ }
@@ -0,0 +1,6 @@
1
+
2
+ {
3
+ "name": "constant-input",
4
+ "depMaterials": ["typings/json-schema"],
5
+ "depPackages": ["@douyinfe/semi-ui"]
6
+ }
@@ -0,0 +1,81 @@
1
+ /* eslint-disable react/prop-types */
2
+ import React, { useMemo } from 'react';
3
+
4
+ import { Input, InputNumber, Select } from '@douyinfe/semi-ui';
5
+
6
+ import { PropsType, Strategy } from './types';
7
+
8
+ const defaultStrategies: Strategy[] = [
9
+ {
10
+ hit: (schema) => schema?.type === 'string',
11
+ Renderer: (props) => (
12
+ <Input placeholder="Please Input String" size="small" disabled={props.readonly} {...props} />
13
+ ),
14
+ },
15
+ {
16
+ hit: (schema) => schema?.type === 'number',
17
+ Renderer: (props) => (
18
+ <InputNumber
19
+ placeholder="Please Input Number"
20
+ size="small"
21
+ disabled={props.readonly}
22
+ hideButtons
23
+ {...props}
24
+ />
25
+ ),
26
+ },
27
+ {
28
+ hit: (schema) => schema?.type === 'integer',
29
+ Renderer: (props) => (
30
+ <InputNumber
31
+ placeholder="Please Input Integer"
32
+ size="small"
33
+ disabled={props.readonly}
34
+ hideButtons
35
+ precision={0}
36
+ {...props}
37
+ />
38
+ ),
39
+ },
40
+ {
41
+ hit: (schema) => schema?.type === 'boolean',
42
+ Renderer: (props) => {
43
+ const { value, onChange, ...rest } = props;
44
+ return (
45
+ <Select
46
+ placeholder="Please Select Boolean"
47
+ size="small"
48
+ disabled={props.readonly}
49
+ optionList={[
50
+ { label: 'True', value: 1 },
51
+ { label: 'False', value: 0 },
52
+ ]}
53
+ value={value ? 1 : 0}
54
+ onChange={(value) => onChange?.(!!value)}
55
+ {...rest}
56
+ />
57
+ );
58
+ },
59
+ },
60
+ ];
61
+
62
+ export function ConstantInput(props: PropsType) {
63
+ const { value, onChange, schema, strategies: extraStrategies, readonly, ...rest } = props;
64
+
65
+ const strategies = useMemo(
66
+ () => [...defaultStrategies, ...(extraStrategies || [])],
67
+ [extraStrategies]
68
+ );
69
+
70
+ const Renderer = useMemo(() => {
71
+ const strategy = strategies.find((_strategy) => _strategy.hit(schema));
72
+
73
+ return strategy?.Renderer;
74
+ }, [strategies, schema]);
75
+
76
+ if (!Renderer) {
77
+ return <Input size="small" disabled placeholder="Unsupported type" />;
78
+ }
79
+
80
+ return <Renderer value={value} onChange={onChange} readonly={readonly} {...rest} />;
81
+ }
@@ -0,0 +1,18 @@
1
+ import { IJsonSchema } from '../../typings';
2
+
3
+ export interface Strategy<Value = any> {
4
+ hit: (schema: IJsonSchema) => boolean;
5
+ Renderer: React.FC<RendererProps<Value>>;
6
+ }
7
+
8
+ export interface RendererProps<Value = any> {
9
+ value?: Value;
10
+ onChange?: (value: Value) => void;
11
+ readonly?: boolean;
12
+ }
13
+
14
+ export interface PropsType extends RendererProps {
15
+ schema: IJsonSchema;
16
+ strategies?: Strategy[];
17
+ [key: string]: any;
18
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "dynamic-value-input",
3
+ "depMaterials": ["flow-value", "constant-input", "variable-selector"],
4
+ "depPackages": ["@douyinfe/semi-ui", "@douyinfe/semi-icons", "styled-components"]
5
+ }
@@ -0,0 +1,85 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import { IconButton } from '@douyinfe/semi-ui';
4
+ import { IconSetting } from '@douyinfe/semi-icons';
5
+
6
+ import { Strategy } from '../constant-input/types';
7
+ import { ConstantInput } from '../constant-input';
8
+ import { IFlowConstantRefValue } from '../../typings/flow-value';
9
+ import { UIContainer, UIMain, UITrigger } from './styles';
10
+ import { VariableSelector } from '../variable-selector';
11
+ import { IJsonSchema } from '../../typings';
12
+
13
+ interface PropsType {
14
+ value?: IFlowConstantRefValue;
15
+ onChange: (value?: IFlowConstantRefValue) => void;
16
+ readonly?: boolean;
17
+ hasError?: boolean;
18
+ style?: React.CSSProperties;
19
+ schema?: IJsonSchema;
20
+ constantProps?: {
21
+ strategies?: Strategy[];
22
+ [key: string]: any;
23
+ };
24
+ }
25
+
26
+ export function DynamicValueInput({
27
+ value,
28
+ onChange,
29
+ readonly,
30
+ style,
31
+ schema,
32
+ constantProps,
33
+ }: PropsType) {
34
+ // When is number type, include integer as well
35
+ const includeSchema = useMemo(() => {
36
+ if (schema?.type === 'number') {
37
+ return [schema, { type: 'integer' }];
38
+ }
39
+ return schema;
40
+ }, [schema]);
41
+
42
+ const renderMain = () => {
43
+ if (value?.type === 'ref') {
44
+ // Display Variable Or Delete
45
+ return (
46
+ <VariableSelector
47
+ value={value?.content}
48
+ onChange={(_v) => onChange(_v ? { type: 'ref', content: _v } : undefined)}
49
+ includeSchema={includeSchema}
50
+ readonly={readonly}
51
+ />
52
+ );
53
+ }
54
+
55
+ return (
56
+ <ConstantInput
57
+ value={value?.content}
58
+ onChange={(_v) => onChange({ type: 'constant', content: _v })}
59
+ schema={schema || { type: 'string' }}
60
+ readonly={readonly}
61
+ {...constantProps}
62
+ />
63
+ );
64
+ };
65
+
66
+ const renderTrigger = () => (
67
+ <VariableSelector
68
+ style={{ width: '100%' }}
69
+ value={value?.type === 'ref' ? value?.content : undefined}
70
+ onChange={(_v) => onChange({ type: 'ref', content: _v })}
71
+ includeSchema={includeSchema}
72
+ readonly={readonly}
73
+ triggerRender={() => (
74
+ <IconButton disabled={readonly} size="small" icon={<IconSetting size="small" />} />
75
+ )}
76
+ />
77
+ );
78
+
79
+ return (
80
+ <UIContainer style={style}>
81
+ <UIMain>{renderMain()}</UIMain>
82
+ <UITrigger>{renderTrigger()}</UITrigger>
83
+ </UIContainer>
84
+ );
85
+ }
@@ -0,0 +1,19 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const UIContainer = styled.div`
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 5px;
7
+ `;
8
+
9
+ export const UIMain = styled.div`
10
+ flex-grow: 1;
11
+
12
+ & .semi-tree-select,
13
+ & .semi-input-number,
14
+ & .semi-select {
15
+ width: 100%;
16
+ }
17
+ `;
18
+
19
+ export const UITrigger = styled.div``;
@@ -0,0 +1,7 @@
1
+ export * from './variable-selector';
2
+ export * from './type-selector';
3
+ export * from './json-schema-editor';
4
+ export * from './batch-variable-selector';
5
+ export * from './constant-input';
6
+ export * from './dynamic-value-input';
7
+ export * from './condition-row';
@@ -0,0 +1,22 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ import Input, { InputProps } from '@douyinfe/semi-ui/lib/es/input';
4
+
5
+ export function BlurInput(props: InputProps) {
6
+ const [value, setValue] = useState('');
7
+
8
+ useEffect(() => {
9
+ setValue(props.value as string);
10
+ }, [props.value]);
11
+
12
+ return (
13
+ <Input
14
+ {...props}
15
+ value={value}
16
+ onChange={(value) => {
17
+ setValue(value);
18
+ }}
19
+ onBlur={(e) => props.onChange?.(value, e)}
20
+ />
21
+ );
22
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "json-schema-editor",
3
+ "depMaterials": ["type-selector", "typings/json-schema"],
4
+ "depPackages": ["@douyinfe/semi-ui", "@douyinfe/semi-icons", "styled-components"]
5
+ }
@@ -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
+ }
@@ -0,0 +1,161 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+
3
+ import { IJsonSchema } from '../../typings';
4
+ import { PropertyValueType } from './types';
5
+
6
+ let _id = 0;
7
+ function genId() {
8
+ return _id++;
9
+ }
10
+
11
+ function getDrilldownSchema(
12
+ value?: PropertyValueType,
13
+ path?: (keyof PropertyValueType)[]
14
+ ): { schema?: PropertyValueType | null; path?: (keyof PropertyValueType)[] } {
15
+ if (!value) {
16
+ return {};
17
+ }
18
+
19
+ if (value.type === 'array' && value.items) {
20
+ return getDrilldownSchema(value.items, [...(path || []), 'items']);
21
+ }
22
+
23
+ return { schema: value, path };
24
+ }
25
+
26
+ export function usePropertiesEdit(
27
+ value?: PropertyValueType,
28
+ onChange?: (value: PropertyValueType) => void
29
+ ) {
30
+ // Get drilldown (array.items.items...)
31
+ const drilldown = useMemo(() => getDrilldownSchema(value), [value, value?.type, value?.items]);
32
+
33
+ const isDrilldownObject = drilldown.schema?.type === 'object';
34
+
35
+ // Generate Init Property List
36
+ const initPropertyList = useMemo(
37
+ () =>
38
+ isDrilldownObject
39
+ ? Object.entries(drilldown.schema?.properties || {})
40
+ .sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
41
+ .map(
42
+ ([name, _value], index) =>
43
+ ({
44
+ key: genId(),
45
+ name,
46
+ isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
47
+ ..._value,
48
+ extra: {
49
+ ...(_value.extra || {}),
50
+ index,
51
+ },
52
+ } as PropertyValueType)
53
+ )
54
+ : [],
55
+ [isDrilldownObject]
56
+ );
57
+
58
+ const [propertyList, setPropertyList] = useState<PropertyValueType[]>(initPropertyList);
59
+
60
+ const mountRef = useRef(false);
61
+
62
+ useEffect(() => {
63
+ // If initRef is true, it means the component has been mounted
64
+ if (mountRef.current) {
65
+ // If the value is changed, update the property list
66
+ setPropertyList((_list) => {
67
+ const nameMap = new Map<string, PropertyValueType>();
68
+
69
+ for (const _property of _list) {
70
+ if (_property.name) {
71
+ nameMap.set(_property.name, _property);
72
+ }
73
+ }
74
+ return Object.entries(drilldown.schema?.properties || {})
75
+ .sort(([, a], [, b]) => (a.extra?.index ?? 0) - (b.extra?.index ?? 0))
76
+ .map(([name, _value]) => {
77
+ const _property = nameMap.get(name);
78
+ if (_property) {
79
+ return {
80
+ key: _property.key,
81
+ name,
82
+ isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
83
+ ..._value,
84
+ };
85
+ }
86
+ return {
87
+ key: genId(),
88
+ name,
89
+ isPropertyRequired: drilldown.schema?.required?.includes(name) || false,
90
+ ..._value,
91
+ };
92
+ });
93
+ });
94
+ }
95
+ mountRef.current = true;
96
+ }, [drilldown.schema]);
97
+
98
+ const updatePropertyList = (updater: (list: PropertyValueType[]) => PropertyValueType[]) => {
99
+ setPropertyList((_list) => {
100
+ const next = updater(_list);
101
+
102
+ // onChange to parent
103
+ const nextProperties: Record<string, IJsonSchema> = {};
104
+ const nextRequired: string[] = [];
105
+
106
+ for (const _property of next) {
107
+ if (!_property.name) {
108
+ continue;
109
+ }
110
+
111
+ nextProperties[_property.name] = _property;
112
+
113
+ if (_property.isPropertyRequired) {
114
+ nextRequired.push(_property.name);
115
+ }
116
+ }
117
+
118
+ let drilldownSchema = value || {};
119
+ if (drilldown.path) {
120
+ drilldownSchema = drilldown.path.reduce((acc, key) => acc[key], value || {});
121
+ }
122
+ drilldownSchema.properties = nextProperties;
123
+ drilldownSchema.required = nextRequired;
124
+
125
+ onChange?.(value || {});
126
+
127
+ return next;
128
+ });
129
+ };
130
+
131
+ const onAddProperty = () => {
132
+ updatePropertyList((_list) => [
133
+ ..._list,
134
+ { key: genId(), name: '', type: 'string', extra: { index: _list.length + 1 } },
135
+ ]);
136
+ };
137
+
138
+ const onRemoveProperty = (key: number) => {
139
+ updatePropertyList((_list) => _list.filter((_property) => _property.key !== key));
140
+ };
141
+
142
+ const onEditProperty = (key: number, nextValue: PropertyValueType) => {
143
+ updatePropertyList((_list) =>
144
+ _list.map((_property) => (_property.key === key ? nextValue : _property))
145
+ );
146
+ };
147
+
148
+ useEffect(() => {
149
+ if (!isDrilldownObject) {
150
+ setPropertyList([]);
151
+ }
152
+ }, [isDrilldownObject]);
153
+
154
+ return {
155
+ propertyList,
156
+ isDrilldownObject,
157
+ onAddProperty,
158
+ onRemoveProperty,
159
+ onEditProperty,
160
+ };
161
+ }