@skopon-cool/form-sdk 0.4.5 → 0.4.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/dist/adapter/a2uiAdapter.d.ts.map +1 -1
- package/dist/adapter/formSchema.d.ts +3 -0
- package/dist/adapter/formSchema.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1353 -1169
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts +4 -2
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts.map +1 -1
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapter/a2uiAdapter.test.ts +25 -0
- package/src/adapter/a2uiAdapter.ts +16 -1
- package/src/adapter/formSchema.test.ts +91 -10
- package/src/adapter/formSchema.ts +209 -17
- package/src/components/AskUserFormCard.tsx +25 -13
- package/src/index.ts +18 -3
- package/src/submit/intersectPayloadBlocksWithForm.ts +27 -4
- package/src/submit/submit.test.ts +54 -0
- package/src/types/index.ts +34 -0
package/src/index.ts
CHANGED
|
@@ -4,13 +4,17 @@ export type {
|
|
|
4
4
|
A2uiComponentNode,
|
|
5
5
|
A2uiSurfaceDoc,
|
|
6
6
|
FormBlock,
|
|
7
|
+
FormBlockCondition,
|
|
7
8
|
FormBlockOption,
|
|
8
9
|
FormBlockType,
|
|
10
|
+
FormCaseFilter,
|
|
11
|
+
FormConditionOperator,
|
|
9
12
|
FormDefinitionPayload,
|
|
10
13
|
FormDetailResult,
|
|
11
14
|
FormFilePlaceholderIcon,
|
|
12
15
|
FormJsonSchema,
|
|
13
16
|
FormMediaSize,
|
|
17
|
+
FormResumeFilter,
|
|
14
18
|
FormSchema,
|
|
15
19
|
SubmitMode,
|
|
16
20
|
} from './types/index'
|
|
@@ -18,9 +22,12 @@ export type {
|
|
|
18
22
|
export {
|
|
19
23
|
A2UI_PROTOCOL_VERSION,
|
|
20
24
|
FORM_MEDIA_SIZES,
|
|
25
|
+
isDisplayBlockType,
|
|
21
26
|
isInputBlockType,
|
|
22
27
|
isLayoutBlockType,
|
|
23
28
|
isMediaBlockType,
|
|
29
|
+
isOptionsBlockType,
|
|
30
|
+
supportsDefaultValue,
|
|
24
31
|
} from './types/index'
|
|
25
32
|
|
|
26
33
|
export {
|
|
@@ -37,7 +44,16 @@ export {
|
|
|
37
44
|
resolveSurfaceFromFormDefinition,
|
|
38
45
|
} from './adapter/resolveSurface'
|
|
39
46
|
|
|
40
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
blocksToJsonSchema,
|
|
49
|
+
coerceToggleValue,
|
|
50
|
+
createEmptyBlock,
|
|
51
|
+
createEmptyFormDefinition,
|
|
52
|
+
normalizeFormDefinition,
|
|
53
|
+
normalizeMultiselectCount,
|
|
54
|
+
resolveSchemaDefaultValue,
|
|
55
|
+
syncFormDefinition,
|
|
56
|
+
} from './adapter/formSchema'
|
|
41
57
|
|
|
42
58
|
export { extractSurfaceValues } from './adapter/extractSurfaceValues'
|
|
43
59
|
|
|
@@ -49,6 +65,7 @@ export {
|
|
|
49
65
|
} from './submit/buildCurlStatement'
|
|
50
66
|
export {
|
|
51
67
|
extractExtraBlockValues,
|
|
68
|
+
getPayloadHiddenBlockValues,
|
|
52
69
|
getPayloadInputFieldNames,
|
|
53
70
|
getPayloadRenderableBlocks,
|
|
54
71
|
intersectPayloadBlocksWithForm,
|
|
@@ -144,6 +161,4 @@ export {
|
|
|
144
161
|
type CaseSearchResult,
|
|
145
162
|
} from './catalog/caseSearchContext'
|
|
146
163
|
|
|
147
|
-
export type { FormResumeFilter, FormCaseFilter } from './types/index'
|
|
148
|
-
|
|
149
164
|
import './styles/index.css'
|
|
@@ -21,6 +21,10 @@ export interface PayloadBlocksIntersection {
|
|
|
21
21
|
|
|
22
22
|
const FORM_ANCHORED_KEYS = new Set(['type', 'name', 'id'])
|
|
23
23
|
|
|
24
|
+
function isHiddenTextBlock(block: FormBlock): boolean {
|
|
25
|
+
return block.type === 'text' && block.hidden === true
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
function normalizePayloadBlock(block: FormBlock): FormBlock {
|
|
25
29
|
return normalizeFormDefinition({
|
|
26
30
|
title: '',
|
|
@@ -50,7 +54,10 @@ function buildRenderBlocks(
|
|
|
50
54
|
const name = payloadBlock.name?.trim()
|
|
51
55
|
if (!name) continue
|
|
52
56
|
const matched = matchedByName.get(name)
|
|
53
|
-
if (matched)
|
|
57
|
+
if (!matched) continue
|
|
58
|
+
// hidden text 不上屏;提交值由 AskUser 从 matchedBlocks 提取
|
|
59
|
+
if (isHiddenTextBlock(matched)) continue
|
|
60
|
+
renderBlocks.push(matched)
|
|
54
61
|
}
|
|
55
62
|
return renderBlocks
|
|
56
63
|
}
|
|
@@ -198,17 +205,33 @@ export function payloadHasInputBlocks(payloadDef: PayloadBlocksDefinition): bool
|
|
|
198
205
|
)
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
/** 交集为空时 fallback:payload 中可上屏的 blocks(layout + input) */
|
|
208
|
+
/** 交集为空时 fallback:payload 中可上屏的 blocks(layout + 非 hidden input) */
|
|
202
209
|
export function getPayloadRenderableBlocks(payloadDef: PayloadBlocksDefinition): FormBlock[] {
|
|
203
210
|
return payloadDef.blocks.filter((block) => {
|
|
204
211
|
if (isLayoutBlockType(block.type)) return true
|
|
205
|
-
|
|
212
|
+
if (!isInputBlockType(block.type) || !block.name?.trim()) return false
|
|
213
|
+
// hidden text 不上屏;提交值由 getPayloadHiddenBlockValues / dataModel 补齐
|
|
214
|
+
return !isHiddenTextBlock(block)
|
|
206
215
|
})
|
|
207
216
|
}
|
|
208
217
|
|
|
209
|
-
/** payload 中所有 input 块 field name(fallback
|
|
218
|
+
/** payload 中所有 input 块 field name(fallback 提交用,含 hidden) */
|
|
210
219
|
export function getPayloadInputFieldNames(payloadDef: PayloadBlocksDefinition): string[] {
|
|
211
220
|
return payloadDef.blocks
|
|
212
221
|
.filter((block) => isInputBlockType(block.type) && block.name?.trim())
|
|
213
222
|
.map((block) => block.name!.trim())
|
|
214
223
|
}
|
|
224
|
+
|
|
225
|
+
/** payload 中 hidden text 的默认提交值(fallback 不上屏时仍需写入 answer) */
|
|
226
|
+
export function getPayloadHiddenBlockValues(
|
|
227
|
+
payloadDef: PayloadBlocksDefinition,
|
|
228
|
+
): Record<string, unknown> {
|
|
229
|
+
const values: Record<string, unknown> = {}
|
|
230
|
+
for (const block of payloadDef.blocks) {
|
|
231
|
+
if (!isHiddenTextBlock(block)) continue
|
|
232
|
+
const name = block.name?.trim()
|
|
233
|
+
if (!name) continue
|
|
234
|
+
values[name] = blockValueFromDefault(block)
|
|
235
|
+
}
|
|
236
|
+
return values
|
|
237
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { buildAnswerAgentBody, validateAnswerAgentBody } from '../submit/buildAnswerAgentBody'
|
|
12
12
|
import {
|
|
13
13
|
extractExtraBlockValues,
|
|
14
|
+
getPayloadHiddenBlockValues,
|
|
14
15
|
getPayloadInputFieldNames,
|
|
15
16
|
getPayloadRenderableBlocks,
|
|
16
17
|
intersectPayloadBlocksWithForm,
|
|
@@ -427,6 +428,59 @@ describe('payload fallback helpers', () => {
|
|
|
427
428
|
expect(blocks.map((b) => b.id)).toEqual(['title', 'q1'])
|
|
428
429
|
})
|
|
429
430
|
|
|
431
|
+
it('excludes hidden text from renderable blocks but keeps submit defaults', () => {
|
|
432
|
+
const withHidden = parsePayloadBlocksJson({
|
|
433
|
+
title: '',
|
|
434
|
+
blocks: [
|
|
435
|
+
{ id: 'title', type: 'heading', label: '问卷调查' },
|
|
436
|
+
{ id: 'q1', type: 'radio', name: 'occupation', label: '职业', options: [] },
|
|
437
|
+
{
|
|
438
|
+
id: 'hint',
|
|
439
|
+
type: 'text',
|
|
440
|
+
name: 'agent_hint',
|
|
441
|
+
label: '提示',
|
|
442
|
+
defaultValue: 'keep-me',
|
|
443
|
+
hidden: true,
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
})!
|
|
447
|
+
expect(getPayloadRenderableBlocks(withHidden).map((b) => b.id)).toEqual(['title', 'q1'])
|
|
448
|
+
expect(getPayloadHiddenBlockValues(withHidden)).toEqual({ agent_hint: 'keep-me' })
|
|
449
|
+
expect(getPayloadInputFieldNames(withHidden)).toEqual(['occupation', 'agent_hint'])
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
it('omits matched hidden text from renderBlocks', () => {
|
|
453
|
+
const formWithHint: FormSchema = {
|
|
454
|
+
title: 'Form',
|
|
455
|
+
description: '',
|
|
456
|
+
blocks: [
|
|
457
|
+
{ id: '1', type: 'text', name: 'video_title', label: 'Title' },
|
|
458
|
+
{ id: '2', type: 'text', name: 'agent_hint', label: 'Hint', hidden: true },
|
|
459
|
+
],
|
|
460
|
+
jsonSchema: {},
|
|
461
|
+
}
|
|
462
|
+
const payload = parsePayloadBlocksJson({
|
|
463
|
+
title: 'Ask',
|
|
464
|
+
blocks: [
|
|
465
|
+
{ id: 'p1', type: 'text', name: 'video_title', label: '视频标题' },
|
|
466
|
+
{
|
|
467
|
+
id: 'p2',
|
|
468
|
+
type: 'text',
|
|
469
|
+
name: 'agent_hint',
|
|
470
|
+
label: '提示',
|
|
471
|
+
defaultValue: 'hint',
|
|
472
|
+
hidden: true,
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
})!
|
|
476
|
+
const result = intersectPayloadBlocksWithForm(payload, formWithHint)
|
|
477
|
+
expect(result.matchedBlocks.map((b) => b.name)).toEqual(['video_title', 'agent_hint'])
|
|
478
|
+
expect(result.renderBlocks.map((b) => b.name)).toEqual(['video_title'])
|
|
479
|
+
expect(extractExtraBlockValues(result.matchedBlocks.filter((b) => b.hidden))).toEqual({
|
|
480
|
+
agent_hint: 'hint',
|
|
481
|
+
})
|
|
482
|
+
})
|
|
483
|
+
|
|
430
484
|
it('returns input field names for submit', () => {
|
|
431
485
|
expect(getPayloadInputFieldNames(surveyPayload)).toEqual(['occupation'])
|
|
432
486
|
})
|
package/src/types/index.ts
CHANGED
|
@@ -86,9 +86,19 @@ export interface FormBlockOption {
|
|
|
86
86
|
label: string
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
export type FormConditionOperator = 'eq' | 'ne' | 'empty' | 'notEmpty'
|
|
90
|
+
|
|
91
|
+
export interface FormBlockCondition {
|
|
92
|
+
fieldName: string
|
|
93
|
+
operator: FormConditionOperator
|
|
94
|
+
value?: string
|
|
95
|
+
}
|
|
96
|
+
|
|
89
97
|
/** 简历多选组件:渲染时用于 /univ/resume/search 的筛选条件 */
|
|
90
98
|
export interface FormResumeFilter {
|
|
91
99
|
names?: string[]
|
|
100
|
+
/** Agent 类型(编辑器辅助,运行时不用) */
|
|
101
|
+
agentKind?: string
|
|
92
102
|
agentUniqueIds?: string[]
|
|
93
103
|
resumeUniqueIds?: string[]
|
|
94
104
|
pageSize?: number
|
|
@@ -127,6 +137,12 @@ export interface FormBlock {
|
|
|
127
137
|
fileMinCount?: number
|
|
128
138
|
fileMaxCount?: number
|
|
129
139
|
defaultValue?: string | string[] | boolean
|
|
140
|
+
/** 派生 JSON Schema 时写入 property.$comment */
|
|
141
|
+
jsonSchemaDescription?: string
|
|
142
|
+
/** 是否隐藏(仅 text 块;隐藏时仍提交) */
|
|
143
|
+
hidden?: boolean
|
|
144
|
+
/** 是否需要 LLM 处理(持久化进 block json) */
|
|
145
|
+
needLLM?: boolean
|
|
130
146
|
/** 简历多选:是否显示「换一批」 */
|
|
131
147
|
resumeEnableRefresh?: boolean
|
|
132
148
|
/** 简历多选:最少选中数量,配置值 ≥ 1 */
|
|
@@ -143,6 +159,7 @@ export interface FormBlock {
|
|
|
143
159
|
caseMaxCount?: number
|
|
144
160
|
/** 案例单选:列表筛选条件(持久化进 block json) */
|
|
145
161
|
caseFilter?: FormCaseFilter
|
|
162
|
+
condition?: FormBlockCondition
|
|
146
163
|
}
|
|
147
164
|
|
|
148
165
|
export interface FormSchema {
|
|
@@ -164,6 +181,23 @@ export function isInputBlockType(type: FormBlockType): boolean {
|
|
|
164
181
|
return !isLayoutBlockType(type) && !isMediaBlockType(type)
|
|
165
182
|
}
|
|
166
183
|
|
|
184
|
+
export function isDisplayBlockType(type: FormBlockType): boolean {
|
|
185
|
+
return isLayoutBlockType(type) || isMediaBlockType(type)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function isOptionsBlockType(type: FormBlockType): boolean {
|
|
189
|
+
return type === 'select' || type === 'multiselect' || type === 'radio' || type === 'checkbox'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function supportsDefaultValue(type: FormBlockType): boolean {
|
|
193
|
+
return (
|
|
194
|
+
isInputBlockType(type) &&
|
|
195
|
+
type !== 'file' &&
|
|
196
|
+
type !== 'resume_multiselect' &&
|
|
197
|
+
type !== 'case_multiselect'
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
167
201
|
export interface FormDefinitionPayload {
|
|
168
202
|
title?: string
|
|
169
203
|
description?: string
|