@baishuyun/ai-design 1.0.2 → 1.0.3

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/.turbo/turbo-build.log +16 -14
  2. package/CHANGELOG.md +9 -0
  3. package/dist/ai-design.js +6255 -5314
  4. package/dist/ai-design.js.map +1 -1
  5. package/dist/ai-design.umd.cjs +67 -67
  6. package/dist/ai-design.umd.cjs.map +1 -1
  7. package/dist/src/components/agent-scroll-selector.d.ts +2 -1
  8. package/dist/src/components/agent-scroll-selector.d.ts.map +1 -1
  9. package/dist/src/components/bs-entry-select/app-group-folder.d.ts +6 -0
  10. package/dist/src/components/bs-entry-select/app-group-folder.d.ts.map +1 -0
  11. package/dist/src/components/bs-entry-select/bs-entry-select.d.ts +4 -0
  12. package/dist/src/components/bs-entry-select/bs-entry-select.d.ts.map +1 -0
  13. package/dist/src/components/bs-entry-select/build-tree.d.ts +9 -0
  14. package/dist/src/components/bs-entry-select/build-tree.d.ts.map +1 -0
  15. package/dist/src/components/bs-entry-select/entry-item.d.ts +8 -0
  16. package/dist/src/components/bs-entry-select/entry-item.d.ts.map +1 -0
  17. package/dist/src/components/bs-entry-select/entry-popover.d.ts +17 -0
  18. package/dist/src/components/bs-entry-select/entry-popover.d.ts.map +1 -0
  19. package/dist/src/components/bs-entry-select/index.d.ts +3 -0
  20. package/dist/src/components/bs-entry-select/index.d.ts.map +1 -0
  21. package/dist/src/components/bs-entry-select/other-app-section.d.ts +11 -0
  22. package/dist/src/components/bs-entry-select/other-app-section.d.ts.map +1 -0
  23. package/dist/src/components/bs-entry-select/scroll-slider.d.ts +10 -0
  24. package/dist/src/components/bs-entry-select/scroll-slider.d.ts.map +1 -0
  25. package/dist/src/components/bs-entry-select/search-input.d.ts +5 -0
  26. package/dist/src/components/bs-entry-select/search-input.d.ts.map +1 -0
  27. package/dist/src/components/bs-entry-select/tree-item-node.d.ts +9 -0
  28. package/dist/src/components/bs-entry-select/tree-item-node.d.ts.map +1 -0
  29. package/dist/src/components/{bs-entry-select.d.ts → bs-entry-select/types.d.ts} +12 -2
  30. package/dist/src/components/bs-entry-select/types.d.ts.map +1 -0
  31. package/dist/src/components/bs-icons.d.ts +2 -0
  32. package/dist/src/components/bs-icons.d.ts.map +1 -1
  33. package/dist/src/components/collapsible.d.ts +6 -0
  34. package/dist/src/components/collapsible.d.ts.map +1 -0
  35. package/dist/src/components/field-checker-list-msg.d.ts +19 -0
  36. package/dist/src/components/field-checker-list-msg.d.ts.map +1 -0
  37. package/dist/src/components/field-checker.d.ts +19 -0
  38. package/dist/src/components/field-checker.d.ts.map +1 -0
  39. package/dist/src/components/field-icon.d.ts +5 -0
  40. package/dist/src/components/field-icon.d.ts.map +1 -0
  41. package/dist/src/components/field-value-checker.d.ts +16 -0
  42. package/dist/src/components/field-value-checker.d.ts.map +1 -0
  43. package/dist/src/components/legacy-slot.d.ts.map +1 -1
  44. package/dist/src/components/office-container.d.ts +5 -0
  45. package/dist/src/components/office-container.d.ts.map +1 -1
  46. package/dist/src/components/subform-fields-value-checker.d.ts +22 -0
  47. package/dist/src/components/subform-fields-value-checker.d.ts.map +1 -0
  48. package/dist/src/const/ui.d.ts +3 -0
  49. package/dist/src/const/ui.d.ts.map +1 -1
  50. package/dist/src/hooks/use-column-appear.d.ts +22 -0
  51. package/dist/src/hooks/use-column-appear.d.ts.map +1 -0
  52. package/dist/src/hooks/use-resizable-width.d.ts +3 -1
  53. package/dist/src/hooks/use-resizable-width.d.ts.map +1 -1
  54. package/dist/src/index.d.ts +11 -2
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/lib/utils.d.ts +3 -0
  57. package/dist/src/lib/utils.d.ts.map +1 -1
  58. package/dist/src/stories/BsEntrySelect.stories.d.ts +4 -0
  59. package/dist/src/stories/BsEntrySelect.stories.d.ts.map +1 -1
  60. package/dist/src/stories/OfficeContainer.stories.d.ts +5 -0
  61. package/dist/src/stories/OfficeContainer.stories.d.ts.map +1 -1
  62. package/dist/src/stories/collapsible.stories.d.ts +8 -0
  63. package/dist/src/stories/collapsible.stories.d.ts.map +1 -0
  64. package/dist/src/stories/field-checker-list-msg.stories.d.ts +11 -0
  65. package/dist/src/stories/field-checker-list-msg.stories.d.ts.map +1 -0
  66. package/dist/src/stories/field-checker.stories.d.ts +13 -0
  67. package/dist/src/stories/field-checker.stories.d.ts.map +1 -0
  68. package/dist/src/stories/field-icon.stories.d.ts +15 -0
  69. package/dist/src/stories/field-icon.stories.d.ts.map +1 -0
  70. package/dist/src/stories/field-value-checker.stories.d.ts +13 -0
  71. package/dist/src/stories/field-value-checker.stories.d.ts.map +1 -0
  72. package/dist/src/stories/subform-fields-value-checker.stories.d.ts +9 -0
  73. package/dist/src/stories/subform-fields-value-checker.stories.d.ts.map +1 -0
  74. package/package.json +7 -5
  75. package/src/components/agent-scroll-selector.tsx +10 -3
  76. package/src/components/bs-entry-select/app-group-folder.tsx +61 -0
  77. package/src/components/bs-entry-select/bs-entry-select.tsx +271 -0
  78. package/src/components/bs-entry-select/build-tree.ts +144 -0
  79. package/src/components/bs-entry-select/entry-item.tsx +43 -0
  80. package/src/components/bs-entry-select/entry-popover.tsx +97 -0
  81. package/src/components/bs-entry-select/index.ts +2 -0
  82. package/src/components/bs-entry-select/other-app-section.tsx +96 -0
  83. package/src/components/bs-entry-select/scroll-slider.tsx +455 -0
  84. package/src/components/bs-entry-select/search-input.tsx +32 -0
  85. package/src/components/bs-entry-select/tree-item-node.tsx +84 -0
  86. package/src/components/bs-entry-select/types.ts +30 -0
  87. package/src/components/bs-icons.tsx +36 -1
  88. package/src/components/card.tsx +1 -1
  89. package/src/components/collapsible.tsx +9 -0
  90. package/src/components/field-checker-list-msg.tsx +146 -0
  91. package/src/components/field-checker.tsx +182 -0
  92. package/src/components/field-icon.tsx +13 -0
  93. package/src/components/field-value-checker.tsx +118 -0
  94. package/src/components/office-container.tsx +111 -28
  95. package/src/components/subform-fields-value-checker.tsx +185 -0
  96. package/src/const/ui.ts +67 -0
  97. package/src/hooks/use-column-appear.ts +78 -0
  98. package/src/hooks/use-resizable-width.ts +10 -2
  99. package/src/index.ts +11 -2
  100. package/src/lib/utils.ts +67 -0
  101. package/src/stories/BsEntrySelect.stories.tsx +45 -2
  102. package/src/stories/OfficeContainer.stories.tsx +150 -1
  103. package/src/stories/collapsible.stories.tsx +47 -0
  104. package/src/stories/field-checker-list-msg.stories.tsx +95 -0
  105. package/src/stories/field-checker.stories.tsx +117 -0
  106. package/src/stories/field-icon.stories.tsx +70 -0
  107. package/src/stories/field-value-checker.stories.tsx +113 -0
  108. package/src/stories/subform-fields-value-checker.stories.tsx +98 -0
  109. package/dist/src/components/bs-entry-select.d.ts.map +0 -1
  110. package/src/components/bs-entry-select.tsx +0 -724
@@ -0,0 +1,118 @@
1
+ import { memo, useEffect } from 'react';
2
+ import { FieldType, ValueOf } from '@baishuyun/types';
3
+ import { FieldTypeNameMap } from '../const/ui';
4
+ import { ToolUIPart } from 'ai';
5
+ import { FieldsTools } from '@baishuyun/agents';
6
+ import { isSubformFieldEqual, toHumanReadable } from '../lib/utils';
7
+ import { SubformFieldsValueChecker } from './subform-fields-value-checker';
8
+ import LineChecker from './line-checker';
9
+
10
+ type Field = ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>;
11
+
12
+ export interface FieldValueCheckerProps {
13
+ field: ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>;
14
+ isSubField?: boolean;
15
+ disabled?: boolean;
16
+ readonly?: boolean;
17
+ onChange?: (checked: boolean, field: Field, parentFieldName?: string, index?: number) => void;
18
+ onLoaded?: (field: Field) => void;
19
+ streaming?: boolean;
20
+ }
21
+
22
+ export const FieldValueChecker = memo(
23
+ ({
24
+ field,
25
+ disabled,
26
+ isSubField,
27
+ readonly,
28
+ streaming,
29
+ onChange,
30
+ onLoaded,
31
+ }: FieldValueCheckerProps) => {
32
+ const { widget, label } = field;
33
+ if (!widget) {
34
+ return null;
35
+ }
36
+
37
+ const { type, value: v } = widget;
38
+
39
+ const t = type as FieldType;
40
+
41
+ const value = toHumanReadable(v, t);
42
+
43
+ if (!FieldTypeNameMap.hasOwnProperty(type)) {
44
+ return null;
45
+ }
46
+
47
+ const isSubForm = type === 'subform';
48
+
49
+ useEffect(() => {
50
+ onLoaded?.(field);
51
+ }, []);
52
+
53
+ const handleCheck = (checked: boolean) => {
54
+ onChange?.(checked, field);
55
+ };
56
+
57
+ const extraPadding = isSubField ? 'pl-6' : '';
58
+
59
+ return (
60
+ <>
61
+ <LineChecker
62
+ streaming={streaming}
63
+ title={label}
64
+ disabled={disabled}
65
+ defaultChecked={true}
66
+ onCheckedChange={handleCheck}
67
+ className={extraPadding}
68
+ readonly={readonly}
69
+ shortDesc={isSubForm ? '' : (value as string)}
70
+ />
71
+ {isSubForm && (
72
+ <SubformFieldsValueChecker
73
+ readonly={readonly}
74
+ subformField={field}
75
+ onChange={(detail) => {
76
+ onChange?.(detail.checked, detail.field, detail.parentFieldName, detail.row);
77
+ }}
78
+ streaming={streaming}
79
+ />
80
+ )}
81
+ </>
82
+ );
83
+ },
84
+ (p1, p2) => {
85
+ // 标签一致
86
+ const isLabelEqual = p1.field?.label === p2.field?.label;
87
+ if (!isLabelEqual) {
88
+ return false;
89
+ }
90
+
91
+ // id 一致
92
+ const isFieldNameEqual = p1.field?.widget?.widgetName === p2.field?.widget?.widgetName;
93
+ if (!isFieldNameEqual) {
94
+ return false;
95
+ }
96
+
97
+ // 禁用状态一致
98
+ const isDisabledEqual = p1.disabled === p2.disabled;
99
+ if (!isDisabledEqual) {
100
+ return false;
101
+ }
102
+
103
+ // 只读状态一致
104
+ const isReadonlyEqual = p1.readonly === p2.readonly;
105
+ if (!isReadonlyEqual) {
106
+ return false;
107
+ }
108
+
109
+ // 流式状态一致
110
+ const isStreamingEqual = p1.streaming === p2.streaming;
111
+ if (!isStreamingEqual) {
112
+ return false;
113
+ }
114
+
115
+ // 进一步比较子表单下的字段
116
+ return isSubformFieldEqual(p1?.field, p2?.field);
117
+ }
118
+ );
@@ -1,80 +1,163 @@
1
- import { FC } from 'react';
1
+ import { FC, useCallback, useRef } from 'react';
2
2
  import { useResizableWidth } from '../hooks/use-resizable-width';
3
+ import { useColumnAppear } from '../hooks/use-column-appear';
3
4
  import classNames from 'classnames';
4
5
 
6
+ export type FullScreenPart = 'leftCol' | 'rightCol' | 'none';
7
+
5
8
  export interface IOfficeContainerProps {
6
9
  headerHeight?: number;
7
10
 
8
11
  hideLeftCol?: boolean;
9
12
 
10
13
  leftColWidthRange?: [number, number];
14
+ rightColWidthRange?: [number, number];
15
+
16
+ leftColCls?: string;
17
+ rightColCls?: string;
18
+
11
19
  leftColHeaderElement?: React.ReactNode;
12
20
  leftColContentElement?: React.ReactNode;
13
21
  rightColHeaderElement?: React.ReactNode;
14
22
  rightColContentElement?: React.ReactNode;
23
+
24
+ fullscreenPart?: FullScreenPart;
15
25
  }
16
26
 
27
+
17
28
  export const OfficeContainer: FC<IOfficeContainerProps> = ({
18
29
  headerHeight = 64,
19
30
  hideLeftCol = false,
20
31
  leftColWidthRange = [360, 500],
32
+ rightColWidthRange,
33
+ leftColCls,
34
+ rightColCls,
21
35
  leftColHeaderElement,
22
36
  leftColContentElement,
23
37
  rightColHeaderElement,
24
38
  rightColContentElement,
39
+ fullscreenPart = 'none',
25
40
  }) => {
26
- const { width: leftColWidth, handleMouseDown, isDragging } = useResizableWidth({
27
- range: leftColWidthRange,
28
- initialWidth: leftColWidthRange[0]
41
+ const isRightResizable = !!rightColWidthRange;
42
+ const activeRange = rightColWidthRange ?? leftColWidthRange;
43
+
44
+ const { width, handleMouseDown, isDragging } = useResizableWidth({
45
+ range: activeRange,
46
+ initialWidth: activeRange[0],
47
+ invertDrag: isRightResizable,
29
48
  });
30
49
 
31
- const borderColorCls = /* isAtEdge ? 'border-[#ff8888]' : */ 'border-[#0265fc]';
50
+ const isLeftFullscreen = fullscreenPart === 'leftCol';
51
+ const isRightFullscreen = fullscreenPart === 'rightCol';
52
+ const isFullscreen = isLeftFullscreen || isRightFullscreen;
53
+
54
+ const leftHidden = hideLeftCol || isRightFullscreen;
55
+ const rightHidden = isLeftFullscreen;
56
+
57
+ // ---- column appear/hide animation ----
58
+
59
+ const {
60
+ colRef: leftColRef,
61
+ getTransitionStyle: leftTransitionStyle,
62
+ } = useColumnAppear({ hidden: leftHidden });
63
+
64
+ const {
65
+ colRef: rightColRef,
66
+ getTransitionStyle: rightTransitionStyle,
67
+ } = useColumnAppear({ hidden: rightHidden });
68
+
69
+ // ---- 列宽计算:始终返回明确像素值,避免 flex 计算宽与 transition 冲突 ----
70
+
71
+ const leftColInnerRef = useRef<HTMLDivElement>(null);
72
+ const rightColInnerRef = useRef<HTMLDivElement>(null);
73
+
74
+ const getColWidth = useCallback((col: 'left' | 'right'): number | string => {
75
+ if (col === 'left') {
76
+ if (hideLeftCol || isRightFullscreen) return 0;
77
+ if (isLeftFullscreen) return '100%';
78
+ if (isRightResizable) {
79
+ if (leftColInnerRef.current) return leftColInnerRef.current.offsetWidth;
80
+ return `calc(100% - ${width}px)`;
81
+ }
82
+ return width;
83
+ }
84
+ // right col
85
+ if (isLeftFullscreen) return 0;
86
+ if (isRightFullscreen) return '100%';
87
+ if (isRightResizable) return width;
88
+ if (rightColInnerRef.current) return rightColInnerRef.current.offsetWidth;
89
+ return `calc(100% - ${width}px)`;
90
+ }, [hideLeftCol, isLeftFullscreen, isRightFullscreen, isRightResizable, width]);
91
+
92
+ const borderColorCls = 'border-[#0265fc]';
93
+
94
+ // 全屏时补全 shrink-0,确保 width: 100% 或 width: 0 不会被 flex 容器挤压
95
+ const leftFlexCls = isFullscreen
96
+ ? 'shrink-0'
97
+ : isRightResizable
98
+ ? 'flex-1'
99
+ : 'shrink-0';
100
+ const rightFlexCls = isFullscreen
101
+ ? 'shrink-0'
102
+ : isRightResizable
103
+ ? 'shrink-0'
104
+ : 'flex-1';
105
+
106
+ // 滚动条使用 scrollbar-gutter: stable 预留空间,避免 overflow 切换时布局抖动
107
+ const contentCls = 'flex-1 min-h-0 overflow-auto w-full';
108
+ const contentStyle: React.CSSProperties = { /*scrollbarGutter: 'stable' */};
32
109
 
33
110
  return (
34
111
  <div className="relative h-full w-full flex overflow-hidden bg-white">
35
112
  {/* left col */}
36
113
  <div
37
- className={classNames("shrink-0 h-full flex flex-col relative bg-[#F5F7FA]", {
38
- "pointer-events-none": hideLeftCol,
39
- "opacity-0": hideLeftCol,
114
+ ref={leftColRef}
115
+ className={classNames("h-full flex flex-col relative", leftColCls, leftFlexCls, {
116
+ "pointer-events-none": hideLeftCol || isRightFullscreen,
117
+ "opacity-0": hideLeftCol || isRightFullscreen,
118
+ "overflow-hidden": /* isRightFullscreen */ true,
40
119
  })}
41
- style={{ width: hideLeftCol ? 0 : leftColWidth }}
120
+ style={{ ...leftTransitionStyle, width: getColWidth('left'), ...(isDragging ? { transition: 'none' } : {}) }}
42
121
  >
43
- <div
44
- className="shrink-0 w-full"
122
+ <div
123
+ ref={leftColInnerRef}
124
+ className="shrink-0 w-full overflow-hidden"
45
125
  style={{ height: headerHeight }}
46
126
  >
47
127
  {leftColHeaderElement}
48
128
  </div>
49
- <div className="flex-1 min-h-0 overflow-auto">
129
+ <div className={contentCls} style={contentStyle}>
50
130
  {leftColContentElement}
51
131
  </div>
52
132
  {/* resizer */}
53
- <div
54
- className={classNames(`h-full w-5 cursor-col-resize z-10 flex justify-center group absolute top-0 right-0 hover:border-r hover:${borderColorCls}`, {
55
- [`border-r ${borderColorCls}`]: isDragging,
56
- })}
57
- onMouseDown={handleMouseDown}
58
- >
59
-
60
- </div>
133
+ {!isFullscreen && (
134
+ <div
135
+ className={classNames(`h-full w-5 cursor-col-resize z-10 flex justify-center group absolute top-0 right-0 hover:border-r hover:${borderColorCls}`, {
136
+ [`border-r ${borderColorCls}`]: isDragging,
137
+ })}
138
+ onMouseDown={handleMouseDown}
139
+ />
140
+ )}
61
141
  </div>
62
142
 
63
-
64
143
  {/* right col */}
65
- <div className={
66
- classNames("flex-1 h-full flex flex-col min-w-0", {
67
- // "shadow-[0_0_16px_rgba(0,0,0,0.10)]": !isDragging,
68
- // "shadow-[0_0_20px_rgba(0,0,0,0.10)]": isDragging,
69
- })
70
- }>
144
+ <div
145
+ ref={rightColRef}
146
+ className={classNames("h-full flex flex-col min-w-0", rightColCls, rightFlexCls, {
147
+ "pointer-events-none": isLeftFullscreen,
148
+ "opacity-0": isLeftFullscreen,
149
+ "overflow-hidden": isLeftFullscreen,
150
+ })}
151
+ style={{ ...rightTransitionStyle, width: getColWidth('right'), ...(isDragging ? { transition: 'none' } : {}) }}
152
+ >
71
153
  <div
154
+ ref={rightColInnerRef}
72
155
  className="shrink-0 w-full"
73
156
  style={{ height: headerHeight }}
74
157
  >
75
158
  {rightColHeaderElement}
76
159
  </div>
77
- <div className="flex-1 min-h-0 overflow-auto">
160
+ <div className={contentCls} style={contentStyle}>
78
161
  {rightColContentElement}
79
162
  </div>
80
163
  </div>
@@ -0,0 +1,185 @@
1
+ import { memo, useCallback, useEffect, useState } from 'react';
2
+ import { FieldType, ValueOf } from '@baishuyun/types';
3
+ import { FieldTypeNameMap } from '../const/ui';
4
+ import { ToolUIPart } from 'ai';
5
+ import { FieldsTools } from '@baishuyun/agents';
6
+ import { toHumanReadable } from '../lib/utils';
7
+ import { Collapsible, CollapsibleContent } from './collapsible';
8
+ import LineChecker from './line-checker';
9
+
10
+ const Checker = ({
11
+ disabled,
12
+ onCheckedChange,
13
+
14
+ label,
15
+ val,
16
+ streaming,
17
+ readonly,
18
+ }: {
19
+ label: string;
20
+ val: string;
21
+ onCheckedChange: (v: boolean) => void;
22
+ disabled?: boolean;
23
+ desc: string;
24
+ readonly?: boolean;
25
+ streaming?: boolean;
26
+ }) => {
27
+ return (
28
+ <LineChecker
29
+ streaming={streaming}
30
+ readonly={readonly}
31
+ title={label}
32
+ disabled={disabled}
33
+ defaultChecked={true}
34
+ onCheckedChange={onCheckedChange}
35
+ className="pl-9"
36
+ shortDesc={val}
37
+ />
38
+ );
39
+ };
40
+
41
+ const SubFieldChecker = memo(
42
+ ({
43
+ field,
44
+ readonly,
45
+ onCheckedChange,
46
+ streaming,
47
+ }: {
48
+ onCheckedChange: (
49
+ checked: boolean,
50
+ field: ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>
51
+ ) => void;
52
+ readonly?: boolean;
53
+ streaming?: boolean;
54
+ field: ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>;
55
+ }) => {
56
+ const { widget, label } = field;
57
+ if (!widget) {
58
+ return null;
59
+ }
60
+
61
+ const { type, value: v } = widget;
62
+ const t = type as FieldType;
63
+ if (!FieldTypeNameMap.hasOwnProperty(t)) {
64
+ return null;
65
+ }
66
+
67
+ const value = toHumanReadable(v, t);
68
+
69
+ const typeDesc = FieldTypeNameMap[t] || FieldTypeNameMap.unknown;
70
+
71
+ const handleCheckChange = useCallback(
72
+ (v: boolean) => {
73
+ onCheckedChange(v, field);
74
+ },
75
+ [field]
76
+ );
77
+
78
+ useEffect(() => {
79
+ // 默认勾选上
80
+ handleCheckChange(true);
81
+ }, []);
82
+
83
+ return (
84
+ <Checker
85
+ streaming={streaming}
86
+ readonly={readonly}
87
+ desc={typeDesc}
88
+ label={label}
89
+ val={value as string}
90
+ onCheckedChange={handleCheckChange}
91
+ />
92
+ );
93
+ },
94
+ (p1, p2) => {
95
+ const isNameEqual =
96
+ p1.field.widget?.widgetName === p2.field.widget?.widgetName &&
97
+ p1.readonly === p2.readonly &&
98
+ p1.streaming === p2.streaming;
99
+ return isNameEqual;
100
+ }
101
+ );
102
+
103
+ type F = ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>;
104
+
105
+ interface generatedField {
106
+ label: string;
107
+ fieldName: string;
108
+ fieldType: string;
109
+ value: any;
110
+ }
111
+
112
+ type onSubformFieldChangeParam = {
113
+ checked: boolean;
114
+ field: F;
115
+ parentFieldName: string;
116
+ row?: number;
117
+ col?: number;
118
+ };
119
+
120
+ /**
121
+ * 专门渲染子表单下的字段勾选
122
+ */
123
+ export const SubformFieldsValueChecker = ({
124
+ subformField,
125
+ onChange,
126
+ streaming,
127
+ readonly,
128
+ }: {
129
+ onChange: (p: onSubformFieldChangeParam) => void;
130
+ subformField: ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>;
131
+ streaming?: boolean;
132
+ readonly?: boolean;
133
+ }) => {
134
+ if (!subformField.widget.value || !subformField.widget.value.length) {
135
+ return null;
136
+ }
137
+
138
+ const subformRecords = subformField.widget.value;
139
+
140
+ // 默认展开
141
+ const [openAll, setOpenAll] = useState(true);
142
+
143
+ return (
144
+ <Collapsible open={openAll} onOpenChange={setOpenAll}>
145
+ <CollapsibleContent>
146
+ {subformRecords.map((rowData: generatedField[], row: number) => {
147
+ if (!rowData || !rowData.length) {
148
+ return null;
149
+ }
150
+ return (
151
+ <Collapsible defaultOpen={true} key={row}>
152
+ <CollapsibleContent>
153
+ {rowData.map((fData: generatedField, col: number) => {
154
+ return (
155
+ <SubFieldChecker
156
+ readonly={readonly}
157
+ streaming={streaming}
158
+ field={{
159
+ label: fData.label,
160
+ widget: {
161
+ widgetName: fData.fieldName,
162
+ type: fData.fieldType,
163
+ value: fData.value,
164
+ },
165
+ }}
166
+ onCheckedChange={(checked: boolean, field: F) => {
167
+ onChange({
168
+ checked,
169
+ field,
170
+ row,
171
+ col,
172
+ parentFieldName: subformField.widget.widgetName,
173
+ });
174
+ }}
175
+ />
176
+ );
177
+ })}
178
+ </CollapsibleContent>
179
+ </Collapsible>
180
+ );
181
+ })}
182
+ </CollapsibleContent>
183
+ </Collapsible>
184
+ );
185
+ };
package/src/const/ui.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FormIconOption } from "../types";
2
+ import { FieldType } from "@baishuyun/types";
2
3
 
3
4
  export const BotImgUrl = `
4
5
  url(data:image/png;base64,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)
@@ -50,3 +51,69 @@ export const FORM_ICON_OPTIONS: FormIconOption[] = [
50
51
  { id: 'biaodan35', code: '&#xe7fb;', label: '表单35', value: 59 },
51
52
  { id: 'biaodan36', code: '&#xe7fa;', label: '表单36', value: 60 },
52
53
  ];
54
+
55
+ export const FieldTypeNameMap: Record<FieldType, string> = {
56
+ text: '输入框',
57
+ password: '密码输入框',
58
+ location: '定位',
59
+ datetime: '日期时间选择器',
60
+ radiogroup: '单选框',
61
+ radio: '单选框',
62
+ combo: '下拉框',
63
+ address: '地址',
64
+ button: '按钮',
65
+ textarea: '多行输入框',
66
+ combocheck: '多选下拉框',
67
+ checkboxgroup: '多选框组',
68
+ separator: '分割线',
69
+ upload: '附件上传',
70
+ number: '数字输入框',
71
+ dataload: '数据加载',
72
+ image: '图片上传',
73
+ signature: '签名',
74
+ dept: '部门选择',
75
+ user: '成员选择',
76
+ usergroup: '成员多选',
77
+ deptgroup: '部门多选',
78
+ unknown: '未知类型',
79
+ sn: '流水号',
80
+ formula: '公式字段',
81
+ linkdata: '关联数据',
82
+ linkquery: '关联查询',
83
+ subform: '子表单',
84
+ flowState: '流程状态',
85
+ flowstate: '流程状态',
86
+ };
87
+
88
+ export const FIELD_ICON_CODE_MAP: Record<FieldType, string> = {
89
+ address: '&#xe881;',
90
+ subform: '&#xe847;',
91
+ password: '&#xe854;',
92
+ button: '&#xe8e8;',
93
+ textarea: '&#xe84f;',
94
+ datetime: '&#xe849;',
95
+ text: '&#xe854;',
96
+ location: '&#xe87f;',
97
+ radiogroup: '&#xe853;',
98
+ radio: '&#xe861;',
99
+ combo: '&#xe84a;',
100
+ formula: '',
101
+ flowState: '',
102
+ flowstate: '',
103
+ combocheck: '&#xe84b;',
104
+ separator: '&#xe850;',
105
+ checkboxgroup: '&#xe84c;',
106
+ upload: '&#xe621;',
107
+ number: '&#xe846;',
108
+ image: '&#xe880;',
109
+ signature: '&#xe87e;',
110
+ user: '&#xe856;',
111
+ dataload: '&#xe848;',
112
+ linkdata: '&#xe857;',
113
+ linkquery: '&#xe857;',
114
+ usergroup: '&#xe851;',
115
+ dept: '&#xe84d;',
116
+ sn: '&#xe882;',
117
+ deptgroup: '&#xe852;',
118
+ unknown: '&#xe848;',
119
+ };
@@ -0,0 +1,78 @@
1
+ import { useLayoutEffect, useRef, useState } from 'react';
2
+
3
+ export const TRANSITION = {
4
+ appearing: 'width 300ms cubic-bezier(0.4,0,0.2,1), opacity 250ms cubic-bezier(0.4,0,0.2,1) 100ms',
5
+ hiding: 'width 350ms cubic-bezier(0.4,0,0.2,1) 60ms, opacity 200ms ease-out',
6
+ neutral: 'width 350ms cubic-bezier(0.4,0,0.2,1), opacity 200ms ease-out',
7
+ } as const;
8
+
9
+ /** hiding 场景最长的 transition 耗时 (width 350ms + delay 60ms),加少量余量用于 transitionend 兜底 */
10
+ export const TRANSITION_FALLBACK_MS = 500;
11
+
12
+ interface UseColumnAppearOptions {
13
+ /** 当前列是否处于隐藏状态 */
14
+ hidden: boolean;
15
+ }
16
+
17
+ interface UseColumnAppearResult {
18
+ /** 列是否正在执行 appear (hidden→visible) 动画 */
19
+ appearing: boolean;
20
+ /** 附加到列最外层 DOM 元素的 ref,用于监听 transitionend */
21
+ colRef: React.RefObject<HTMLDivElement>;
22
+ /** 当前适用的 transition CSS 属性,直接展开到 style={} */
23
+ getTransitionStyle: React.CSSProperties;
24
+ }
25
+
26
+ export const useColumnAppear = ({ hidden }: UseColumnAppearOptions): UseColumnAppearResult => {
27
+ const [appearing, setAppearing] = useState(false);
28
+ const prevHiddenRef = useRef<boolean | null>(null);
29
+ const colRef = useRef<HTMLDivElement>(null);
30
+
31
+ // 检测 hidden → visible 转换,触发 appearing
32
+ useLayoutEffect(() => {
33
+ if (prevHiddenRef.current === null) {
34
+ prevHiddenRef.current = hidden;
35
+ return;
36
+ }
37
+ if (prevHiddenRef.current && !hidden) {
38
+ setAppearing(true);
39
+ }
40
+ prevHiddenRef.current = hidden;
41
+ }, [hidden]);
42
+
43
+ // transitionend 监听 + 兜底 timeout 清除 appearing
44
+ useLayoutEffect(() => {
45
+ if (!appearing) return;
46
+ const el = colRef.current;
47
+ if (!el) return;
48
+
49
+ const onTransitionEnd = (e: TransitionEvent) => {
50
+ if (e.propertyName === 'width') {
51
+ setAppearing(false);
52
+ }
53
+
54
+ window.dispatchEvent(new Event('resize'));
55
+ };
56
+ el.addEventListener('transitionend', onTransitionEnd);
57
+
58
+ const fallback = setTimeout(() => setAppearing(false), TRANSITION_FALLBACK_MS);
59
+
60
+ return () => {
61
+ el.removeEventListener('transitionend', onTransitionEnd);
62
+ clearTimeout(fallback);
63
+ };
64
+ }, [appearing]);
65
+
66
+ // 计算当前适用的 transition 样式
67
+ const willChange = hidden || appearing ? ({ willChange: 'width, opacity' } as const) : {};
68
+ let transitionStyle: React.CSSProperties;
69
+ if (appearing) {
70
+ transitionStyle = { transition: TRANSITION.appearing, ...willChange };
71
+ } else if (hidden) {
72
+ transitionStyle = { transition: TRANSITION.hiding, ...willChange };
73
+ } else {
74
+ transitionStyle = { transition: TRANSITION.neutral };
75
+ }
76
+
77
+ return { appearing, colRef, getTransitionStyle: transitionStyle };
78
+ };