@skopon-cool/form-sdk 0.3.2 → 0.4.1
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/catalog/skoponCaseSelect.d.ts.map +1 -1
- package/dist/catalog/skoponResumeSelect.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/index.js +1386 -1173
- package/dist/runtime/buildFormRuntimeSnapshot.d.ts +16 -0
- package/dist/runtime/buildFormRuntimeSnapshot.d.ts.map +1 -0
- package/dist/runtime/formLiveOptionsContext.d.ts +20 -0
- package/dist/runtime/formLiveOptionsContext.d.ts.map +1 -0
- package/dist/runtime/formRuntimeConnection.d.ts +35 -0
- package/dist/runtime/formRuntimeConnection.d.ts.map +1 -0
- package/dist/runtime/formRuntimeContext.d.ts +2 -2
- package/dist/runtime/formRuntimeContext.d.ts.map +1 -1
- package/dist/runtime/formRuntimeWs.d.ts +8 -15
- package/dist/runtime/formRuntimeWs.d.ts.map +1 -1
- package/dist/runtime/types.d.ts +11 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/catalog/skoponCaseSelect.tsx +12 -1
- package/src/catalog/skoponResumeSelect.tsx +12 -1
- package/src/components/AskUserFormCard.tsx +112 -13
- package/src/runtime/buildFormRuntimeSnapshot.test.ts +97 -0
- package/src/runtime/buildFormRuntimeSnapshot.ts +61 -0
- package/src/runtime/formLiveOptionsContext.tsx +118 -0
- package/src/runtime/formRuntimeConnection.test.ts +155 -0
- package/src/runtime/formRuntimeConnection.ts +251 -0
- package/src/runtime/formRuntimeContext.tsx +13 -11
- package/src/runtime/formRuntimeWs.ts +24 -134
- package/src/runtime/types.ts +12 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FormBlock, FormBlockOption } from '../types/index';
|
|
2
|
+
import type { FormRuntimeDataPayload } from './types';
|
|
3
|
+
export type LiveOptionsMap = ReadonlyMap<string, FormBlockOption[]>;
|
|
4
|
+
export interface BuildFormRuntimeSnapshotInput {
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
blocks: FormBlock[];
|
|
8
|
+
values: Record<string, unknown>;
|
|
9
|
+
liveOptions?: LiveOptionsMap;
|
|
10
|
+
formChange?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 构建 form_runtime_data 快照:form_schema(block 格式)+ 扁平 form_values + form_change。
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildFormRuntimeSnapshot(input: BuildFormRuntimeSnapshotInput): FormRuntimeDataPayload;
|
|
16
|
+
//# sourceMappingURL=buildFormRuntimeSnapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildFormRuntimeSnapshot.d.ts","sourceRoot":"","sources":["../../src/runtime/buildFormRuntimeSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAErD,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;AA0BnE,MAAM,WAAW,6BAA6B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,6BAA6B,GACnC,sBAAsB,CAiBxB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { FormBlockOption } from '../types/index';
|
|
3
|
+
export type PublishLiveOptionsFn = (fieldName: string, options: FormBlockOption[]) => void;
|
|
4
|
+
export interface FormLiveOptionsContextValue {
|
|
5
|
+
publishLiveOptions: PublishLiveOptionsFn;
|
|
6
|
+
/** 当前 live options 的不可变快照(供读取)。 */
|
|
7
|
+
liveOptions: ReadonlyMap<string, FormBlockOption[]>;
|
|
8
|
+
/** 最近一次签名变化导致的变更字段(debounce 归集前由 Provider 消费后清空)。 */
|
|
9
|
+
pendingChangeFields: readonly string[];
|
|
10
|
+
/** 版本号:live options 有实质变更时递增,用于触发上报 effect。 */
|
|
11
|
+
version: number;
|
|
12
|
+
clearPendingChangeFields: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface FormLiveOptionsProviderProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare function FormLiveOptionsProvider({ children }: FormLiveOptionsProviderProps): import("react").JSX.Element;
|
|
18
|
+
/** 无 Provider 时 no-op(编辑器预览不受影响)。 */
|
|
19
|
+
export declare function useFormLiveOptions(): FormLiveOptionsContextValue;
|
|
20
|
+
//# sourceMappingURL=formLiveOptionsContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formLiveOptionsContext.d.ts","sourceRoot":"","sources":["../../src/runtime/formLiveOptionsContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,MAAM,MAAM,oBAAoB,GAAG,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,eAAe,EAAE,KACvB,IAAI,CAAA;AAET,MAAM,WAAW,2BAA2B;IAC1C,kBAAkB,EAAE,oBAAoB,CAAA;IACxC,mCAAmC;IACnC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;IACnD,qDAAqD;IACrD,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf,wBAAwB,EAAE,MAAM,IAAI,CAAA;CACrC;AAsBD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,uBAAuB,CAAC,EAAE,QAAQ,EAAE,EAAE,4BAA4B,+BA6DjF;AAED,qCAAqC;AACrC,wBAAgB,kBAAkB,IAAI,2BAA2B,CAEhE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FormAutoControlEventData, FormRuntimeEnvelope, FormRuntimeOutboundType } from './types';
|
|
2
|
+
export type FormAutoControlHandler = (data: FormAutoControlEventData, envelope: FormRuntimeEnvelope<FormAutoControlEventData>) => void;
|
|
3
|
+
/** 测试用:清空所有单例连接。 */
|
|
4
|
+
export declare function resetFormRuntimeConnectionsForTests(): void;
|
|
5
|
+
export declare function acquireFormRuntimeConnection(url: string): FormRuntimeConnection;
|
|
6
|
+
export declare class FormRuntimeConnection {
|
|
7
|
+
private readonly url;
|
|
8
|
+
private ws;
|
|
9
|
+
private refCount;
|
|
10
|
+
private connectRequested;
|
|
11
|
+
private reconnectAttempt;
|
|
12
|
+
private reconnectTimer;
|
|
13
|
+
private releaseTimer;
|
|
14
|
+
private sendQueue;
|
|
15
|
+
private readonly handlers;
|
|
16
|
+
constructor(url: string);
|
|
17
|
+
acquire(): void;
|
|
18
|
+
release(): void;
|
|
19
|
+
/** 强制关闭(测试用),跳过宽限期。 */
|
|
20
|
+
forceClose(): void;
|
|
21
|
+
subscribe(askUserMessageId: string, handler: FormAutoControlHandler): () => void;
|
|
22
|
+
/** 懒建连:首个会话 connect() 时才真正打开 socket。 */
|
|
23
|
+
connect(): void;
|
|
24
|
+
send(askUserMessageId: string, type: FormRuntimeOutboundType, data: Record<string, unknown>): void;
|
|
25
|
+
private openSocket;
|
|
26
|
+
private teardown;
|
|
27
|
+
private disposeSocket;
|
|
28
|
+
private enqueue;
|
|
29
|
+
private flushQueue;
|
|
30
|
+
private scheduleReconnect;
|
|
31
|
+
private clearReconnectTimer;
|
|
32
|
+
private clearReleaseTimer;
|
|
33
|
+
private handleMessage;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=formRuntimeConnection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formRuntimeConnection.d.ts","sourceRoot":"","sources":["../../src/runtime/formRuntimeConnection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,SAAS,CAAA;AAMhB,MAAM,MAAM,sBAAsB,GAAG,CACnC,IAAI,EAAE,wBAAwB,EAC9B,QAAQ,EAAE,mBAAmB,CAAC,wBAAwB,CAAC,KACpD,IAAI,CAAA;AAIT,oBAAoB;AACpB,wBAAgB,mCAAmC,IAAI,IAAI,CAK1D;AAED,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAS/E;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,YAAY,CAA6C;IACjE,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiD;gBAE9D,GAAG,EAAE,MAAM;IAIvB,OAAO,IAAI,IAAI;IAKf,OAAO,IAAI,IAAI;IAcf,uBAAuB;IACvB,UAAU,IAAI,IAAI;IAQlB,SAAS,CAAC,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAiBhF,wCAAwC;IACxC,OAAO,IAAI,IAAI;IASf,IAAI,CACF,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,uBAAuB,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,IAAI;IAqBP,OAAO,CAAC,UAAU;IA2BlB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,aAAa;CAyBtB"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { FormRuntimeWsClient } from './formRuntimeWs';
|
|
3
|
-
import type { FormRuntimeEventData } from './types';
|
|
3
|
+
import type { FormRuntimeDataPayload, FormRuntimeEventData } from './types';
|
|
4
4
|
export interface FormRuntimeContextValue {
|
|
5
5
|
reportEvent: (event: FormRuntimeEventData) => void;
|
|
6
|
-
reportData: (
|
|
6
|
+
reportData: (payload: FormRuntimeDataPayload) => void;
|
|
7
7
|
/**
|
|
8
8
|
* 在自动控上下文中执行 fn。若 fn 返回 Promise,depth 保持到 settle,
|
|
9
9
|
* 使异步 submit 内的 reportEvent 仍为 form_auto_control_bac_event。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formRuntimeContext.d.ts","sourceRoot":"","sources":["../../src/runtime/formRuntimeContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"formRuntimeContext.d.ts","sourceRoot":"","sources":["../../src/runtime/formRuntimeContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAE3E,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAClD,UAAU,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAA;IACrD;;;OAGG;IACH,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;IACrC,aAAa,EAAE,MAAM,OAAO,CAAA;CAC7B;AA+BD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAClC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,QAAQ,GACT,EAAE,wBAAwB,+BAkE1B;AAED,uCAAuC;AACvC,wBAAgB,cAAc,IAAI,uBAAuB,CAExD"}
|
|
@@ -1,29 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { type FormAutoControlHandler } from './formRuntimeConnection';
|
|
2
|
+
import type { FormRuntimeOutboundType } from './types';
|
|
3
|
+
export type { FormAutoControlHandler } from './formRuntimeConnection';
|
|
3
4
|
export interface FormRuntimeWsClientOptions {
|
|
4
5
|
url: string;
|
|
5
6
|
askUserMessageId: string;
|
|
6
7
|
onAutoControl?: FormAutoControlHandler;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Per-card runtime 会话:绑定 ask_user_message_id,代理到按 URL 的全局单例连接。
|
|
11
|
+
*/
|
|
8
12
|
export declare class FormRuntimeWsClient {
|
|
9
|
-
private readonly url;
|
|
10
13
|
private readonly askUserMessageId;
|
|
11
|
-
private readonly
|
|
12
|
-
private
|
|
14
|
+
private readonly connection;
|
|
15
|
+
private readonly unsubscribe;
|
|
13
16
|
private closed;
|
|
14
|
-
private reconnectAttempt;
|
|
15
|
-
private reconnectTimer;
|
|
16
|
-
private sendQueue;
|
|
17
17
|
constructor(options: FormRuntimeWsClientOptions);
|
|
18
18
|
connect(): void;
|
|
19
19
|
send(type: FormRuntimeOutboundType, data: Record<string, unknown>): void;
|
|
20
20
|
close(): void;
|
|
21
|
-
/** 关闭当前 socket 且不触发重连(清掉 onclose),建新连或主动 close 时用。 */
|
|
22
|
-
private disposeSocket;
|
|
23
|
-
private enqueue;
|
|
24
|
-
private flushQueue;
|
|
25
|
-
private scheduleReconnect;
|
|
26
|
-
private clearReconnectTimer;
|
|
27
|
-
private handleMessage;
|
|
28
21
|
}
|
|
29
22
|
//# sourceMappingURL=formRuntimeWs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formRuntimeWs.d.ts","sourceRoot":"","sources":["../../src/runtime/formRuntimeWs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"formRuntimeWs.d.ts","sourceRoot":"","sources":["../../src/runtime/formRuntimeWs.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAE5B,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAEtD,YAAY,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAErE,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAA;IACX,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,sBAAsB,CAAA;CACvC;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAY;IACxC,OAAO,CAAC,MAAM,CAAQ;gBAEV,OAAO,EAAE,0BAA0B;IAS/C,OAAO,IAAI,IAAI;IAKf,IAAI,CACF,IAAI,EAAE,uBAAuB,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,IAAI;IAKP,KAAK,IAAI,IAAI;CAMd"}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -17,4 +17,15 @@ export interface FormRuntimeEnvelope<T = Record<string, unknown>> {
|
|
|
17
17
|
ask_user_message_id: string;
|
|
18
18
|
data: T;
|
|
19
19
|
}
|
|
20
|
+
/** form_runtime_data 的 data 载荷(Init / Refresh / Submit)。 */
|
|
21
|
+
export interface FormRuntimeDataPayload {
|
|
22
|
+
form_schema: {
|
|
23
|
+
title?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
blocks: unknown[];
|
|
26
|
+
};
|
|
27
|
+
form_values: Record<string, unknown>;
|
|
28
|
+
/** 本次被更新的字段名;Init/Submit 为空数组。 */
|
|
29
|
+
form_change: string[];
|
|
30
|
+
}
|
|
20
31
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AAElF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,iBAAiB,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,iBAAiB,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,uBAAuB,GAC/B,mBAAmB,GACnB,oBAAoB,GACpB,6BAA6B,CAAA;AAEjC,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB,EAAE,MAAM,CAAA;IAC3B,IAAI,EAAE,CAAC,CAAA;CACR"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AAElF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,iBAAiB,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,iBAAiB,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,uBAAuB,GAC/B,mBAAmB,GACnB,oBAAoB,GACpB,6BAA6B,CAAA;AAEjC,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB,EAAE,MAAM,CAAA;IAC3B,IAAI,EAAE,CAAC,CAAA;CACR;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,MAAM,EAAE,OAAO,EAAE,CAAA;KAClB,CAAA;IACD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,kCAAkC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB"}
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import type { ComponentContext } from '@a2ui/web_core/v0_9'
|
|
|
5
5
|
import { createBinderlessComponentImplementation } from '@a2ui/react/v0_9'
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import type { FormCaseFilter } from '../types/index'
|
|
8
|
+
import { useFormLiveOptions } from '../runtime/formLiveOptionsContext'
|
|
8
9
|
import { useA2uiPreviewMode } from './a2uiPreviewContext'
|
|
9
10
|
import { asStringArray, clampStringArraySelection, isCardSelectionDisabled, readOptionalSelectCount, toggleStringArraySelection, useSkoponBoundField } from './useSkoponBoundField'
|
|
10
11
|
import { type CaseSearchItem, useCaseSearch } from './caseSearchContext'
|
|
@@ -110,6 +111,7 @@ function CaseCard({
|
|
|
110
111
|
function SkoponCaseSelectPreview({ context }: { context: ComponentContext }) {
|
|
111
112
|
const { interactive } = useA2uiPreviewMode()
|
|
112
113
|
const caseSearch = useCaseSearch()
|
|
114
|
+
const { publishLiveOptions } = useFormLiveOptions()
|
|
113
115
|
const { value, setValue, setValueSilent, fieldName } = useSkoponBoundField(context, 'value', {
|
|
114
116
|
component: 'SkoponCaseSelect',
|
|
115
117
|
changeAction: 'click',
|
|
@@ -167,6 +169,15 @@ function SkoponCaseSelectPreview({ context }: { context: ComponentContext }) {
|
|
|
167
169
|
setTotal(result.total)
|
|
168
170
|
setPage(result.page)
|
|
169
171
|
setPageSize(result.pageSize)
|
|
172
|
+
if (fieldName) {
|
|
173
|
+
publishLiveOptions(
|
|
174
|
+
fieldName,
|
|
175
|
+
result.list.map((item) => ({
|
|
176
|
+
value: item.caseUniqueId,
|
|
177
|
+
label: item.name || item.caseUniqueId,
|
|
178
|
+
})),
|
|
179
|
+
)
|
|
180
|
+
}
|
|
170
181
|
} catch (err) {
|
|
171
182
|
if (seq !== requestSeqRef.current) return
|
|
172
183
|
setItems([])
|
|
@@ -176,7 +187,7 @@ function SkoponCaseSelectPreview({ context }: { context: ComponentContext }) {
|
|
|
176
187
|
if (seq === requestSeqRef.current) setLoading(false)
|
|
177
188
|
}
|
|
178
189
|
},
|
|
179
|
-
[caseSearch, filterSig],
|
|
190
|
+
[caseSearch, filterSig, fieldName, publishLiveOptions],
|
|
180
191
|
)
|
|
181
192
|
|
|
182
193
|
useEffect(() => {
|
|
@@ -5,6 +5,7 @@ import type { ComponentContext } from '@a2ui/web_core/v0_9'
|
|
|
5
5
|
import { createBinderlessComponentImplementation } from '@a2ui/react/v0_9'
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import type { FormResumeFilter } from '../types/index'
|
|
8
|
+
import { useFormLiveOptions } from '../runtime/formLiveOptionsContext'
|
|
8
9
|
import { useA2uiPreviewMode } from './a2uiPreviewContext'
|
|
9
10
|
import { asStringArray, clampStringArraySelection, isCardSelectionDisabled, readOptionalSelectCount, toggleStringArraySelection, useSkoponBoundField } from './useSkoponBoundField'
|
|
10
11
|
import {
|
|
@@ -163,6 +164,7 @@ function ResumeCard({
|
|
|
163
164
|
function SkoponResumeSelectPreview({ context }: { context: ComponentContext }) {
|
|
164
165
|
const { interactive } = useA2uiPreviewMode()
|
|
165
166
|
const resumeSearch = useResumeSearch()
|
|
167
|
+
const { publishLiveOptions } = useFormLiveOptions()
|
|
166
168
|
const { value, setValue, setValueSilent, fieldName } = useSkoponBoundField(context, 'value', {
|
|
167
169
|
component: 'SkoponResumeSelect',
|
|
168
170
|
changeAction: 'click',
|
|
@@ -213,6 +215,15 @@ function SkoponResumeSelectPreview({ context }: { context: ComponentContext }) {
|
|
|
213
215
|
setTotal(result.total)
|
|
214
216
|
setPage(result.page)
|
|
215
217
|
setPageSize(result.pageSize)
|
|
218
|
+
if (fieldName) {
|
|
219
|
+
publishLiveOptions(
|
|
220
|
+
fieldName,
|
|
221
|
+
result.list.map((item) => ({
|
|
222
|
+
value: item.resumeUniqueId,
|
|
223
|
+
label: item.name || item.resumeUniqueId,
|
|
224
|
+
})),
|
|
225
|
+
)
|
|
226
|
+
}
|
|
216
227
|
} catch (err) {
|
|
217
228
|
if (seq !== requestSeqRef.current) return
|
|
218
229
|
setItems([])
|
|
@@ -222,7 +233,7 @@ function SkoponResumeSelectPreview({ context }: { context: ComponentContext }) {
|
|
|
222
233
|
if (seq === requestSeqRef.current) setLoading(false)
|
|
223
234
|
}
|
|
224
235
|
},
|
|
225
|
-
[resumeSearch, filterSig],
|
|
236
|
+
[resumeSearch, filterSig, fieldName, publishLiveOptions],
|
|
226
237
|
)
|
|
227
238
|
|
|
228
239
|
useEffect(() => {
|
|
@@ -31,10 +31,17 @@ import { FormRuntimeProvider, useFormRuntime } from '../runtime/formRuntimeConte
|
|
|
31
31
|
import { FormRuntimeWsClient } from '../runtime/formRuntimeWs'
|
|
32
32
|
import { executeFormAutoControl } from '../runtime/formAutoControl'
|
|
33
33
|
import type { FormAutoControlEventData } from '../runtime/types'
|
|
34
|
+
import { buildFormRuntimeSnapshot } from '../runtime/buildFormRuntimeSnapshot'
|
|
35
|
+
import {
|
|
36
|
+
FormLiveOptionsProvider,
|
|
37
|
+
useFormLiveOptions,
|
|
38
|
+
} from '../runtime/formLiveOptionsContext'
|
|
34
39
|
import SkoponFormRenderer, { type SkoponFormRendererRef } from './SkoponFormRenderer'
|
|
35
40
|
import CurlSubmitBlock from './CurlSubmitBlock'
|
|
36
41
|
import SkoponFormProvider from './SkoponFormProvider'
|
|
37
42
|
|
|
43
|
+
const REFRESH_REPORT_DEBOUNCE_MS = 80
|
|
44
|
+
|
|
38
45
|
function formatSubmitErrorMessage(status: number, body: unknown): string {
|
|
39
46
|
if (body && typeof body === 'object' && body !== null && 'message' in body) {
|
|
40
47
|
const msg = (body as { message: unknown }).message
|
|
@@ -101,6 +108,9 @@ function AskUserFormCardBody({
|
|
|
101
108
|
const onNotifyRef = useRef(onNotify)
|
|
102
109
|
onNotifyRef.current = onNotify
|
|
103
110
|
const runtime = useFormRuntime()
|
|
111
|
+
const liveOptionsCtx = useFormLiveOptions()
|
|
112
|
+
const lastInitKeyRef = useRef('')
|
|
113
|
+
const refreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
104
114
|
|
|
105
115
|
const resolvedFormUniqueId = formUniqueId?.trim() ?? ''
|
|
106
116
|
const resolvedAskUserMessageId = askUserMessageId?.trim() ?? ''
|
|
@@ -122,6 +132,10 @@ function AskUserFormCardBody({
|
|
|
122
132
|
mountedRef.current = true
|
|
123
133
|
return () => {
|
|
124
134
|
mountedRef.current = false
|
|
135
|
+
if (refreshTimerRef.current != null) {
|
|
136
|
+
clearTimeout(refreshTimerRef.current)
|
|
137
|
+
refreshTimerRef.current = null
|
|
138
|
+
}
|
|
125
139
|
}
|
|
126
140
|
}, [])
|
|
127
141
|
|
|
@@ -193,12 +207,25 @@ function AskUserFormCardBody({
|
|
|
193
207
|
.map((block) => block.name!.trim())
|
|
194
208
|
}, [usePayloadFallback, payloadDef, renderBlocks])
|
|
195
209
|
|
|
210
|
+
const surfaceTitle = intersection?.title ?? payloadDef?.title ?? ''
|
|
211
|
+
const surfaceDescription = intersection?.description ?? payloadDef?.description ?? ''
|
|
212
|
+
|
|
196
213
|
const surfaceDoc = useMemo(() => {
|
|
197
214
|
if (renderBlocks.length === 0) return null
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
215
|
+
return buildAskUserSurface(
|
|
216
|
+
{ title: surfaceTitle, description: surfaceDescription },
|
|
217
|
+
renderBlocks,
|
|
218
|
+
)
|
|
219
|
+
}, [surfaceTitle, surfaceDescription, renderBlocks])
|
|
220
|
+
|
|
221
|
+
const docKey = useMemo(() => {
|
|
222
|
+
if (!surfaceDoc) return ''
|
|
223
|
+
try {
|
|
224
|
+
return JSON.stringify(surfaceDoc)
|
|
225
|
+
} catch {
|
|
226
|
+
return String(surfaceDoc)
|
|
227
|
+
}
|
|
228
|
+
}, [surfaceDoc])
|
|
202
229
|
|
|
203
230
|
const surfaceId = resolvedFormUniqueId
|
|
204
231
|
? `ask-user-${resolvedFormUniqueId}`
|
|
@@ -211,6 +238,76 @@ function AskUserFormCardBody({
|
|
|
211
238
|
return () => onSurfaceReadyChange(false)
|
|
212
239
|
}, [hasFormSurface, onSurfaceReadyChange])
|
|
213
240
|
|
|
241
|
+
const collectAnswer = useCallback(() => {
|
|
242
|
+
const cardValues = rendererRef.current?.getValues(fieldNames) ?? {}
|
|
243
|
+
return { ...cardValues, ...extraValues }
|
|
244
|
+
}, [fieldNames, extraValues])
|
|
245
|
+
|
|
246
|
+
// live options 走 ref,避免 options 刷新导致 reportSnapshot 引用变化、打断 Init 定时器
|
|
247
|
+
const liveOptionsRef = useRef(liveOptionsCtx.liveOptions)
|
|
248
|
+
liveOptionsRef.current = liveOptionsCtx.liveOptions
|
|
249
|
+
const pendingChangeRef = useRef(liveOptionsCtx.pendingChangeFields)
|
|
250
|
+
pendingChangeRef.current = liveOptionsCtx.pendingChangeFields
|
|
251
|
+
|
|
252
|
+
const reportSnapshot = useCallback(
|
|
253
|
+
(formChange: string[]) => {
|
|
254
|
+
const snapshot = buildFormRuntimeSnapshot({
|
|
255
|
+
title: surfaceTitle,
|
|
256
|
+
description: surfaceDescription,
|
|
257
|
+
blocks: renderBlocks,
|
|
258
|
+
values: collectAnswer(),
|
|
259
|
+
liveOptions: liveOptionsRef.current,
|
|
260
|
+
formChange,
|
|
261
|
+
})
|
|
262
|
+
runtime.reportData(snapshot)
|
|
263
|
+
},
|
|
264
|
+
[surfaceTitle, surfaceDescription, renderBlocks, collectAnswer, runtime],
|
|
265
|
+
)
|
|
266
|
+
const reportSnapshotRef = useRef(reportSnapshot)
|
|
267
|
+
reportSnapshotRef.current = reportSnapshot
|
|
268
|
+
|
|
269
|
+
// Init:surface 就绪或 doc 内容变化时上报(form_change: [])
|
|
270
|
+
// lastInitKey 仅在实际上报后写入;定时器被取消时不占位,避免 Init 被永久跳过。
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!hasFormSurface) {
|
|
273
|
+
lastInitKeyRef.current = ''
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
if (!docKey) return
|
|
277
|
+
const initKey = `${surfaceId}::${docKey}`
|
|
278
|
+
if (lastInitKeyRef.current === initKey) return
|
|
279
|
+
const timer = setTimeout(() => {
|
|
280
|
+
if (!mountedRef.current) return
|
|
281
|
+
if (lastInitKeyRef.current === initKey) return
|
|
282
|
+
lastInitKeyRef.current = initKey
|
|
283
|
+
reportSnapshotRef.current([])
|
|
284
|
+
}, 0)
|
|
285
|
+
return () => clearTimeout(timer)
|
|
286
|
+
}, [hasFormSurface, surfaceId, docKey])
|
|
287
|
+
|
|
288
|
+
// Refresh:live options 变更后 debounce 上报(form_change 为变更字段)
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (!hasFormSurface || liveOptionsCtx.version === 0) return
|
|
291
|
+
if (pendingChangeRef.current.length === 0) return
|
|
292
|
+
if (refreshTimerRef.current != null) {
|
|
293
|
+
clearTimeout(refreshTimerRef.current)
|
|
294
|
+
}
|
|
295
|
+
refreshTimerRef.current = setTimeout(() => {
|
|
296
|
+
refreshTimerRef.current = null
|
|
297
|
+
if (!mountedRef.current) return
|
|
298
|
+
const changed = [...pendingChangeRef.current]
|
|
299
|
+
liveOptionsCtx.clearPendingChangeFields()
|
|
300
|
+
if (changed.length === 0) return
|
|
301
|
+
reportSnapshotRef.current(changed)
|
|
302
|
+
}, REFRESH_REPORT_DEBOUNCE_MS)
|
|
303
|
+
return () => {
|
|
304
|
+
if (refreshTimerRef.current != null) {
|
|
305
|
+
clearTimeout(refreshTimerRef.current)
|
|
306
|
+
refreshTimerRef.current = null
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}, [hasFormSurface, liveOptionsCtx.version, liveOptionsCtx.clearPendingChangeFields])
|
|
310
|
+
|
|
214
311
|
const handleSubmit = useCallback(async () => {
|
|
215
312
|
if (submitting) return
|
|
216
313
|
setSubmitting(true)
|
|
@@ -229,8 +326,7 @@ function AskUserFormCardBody({
|
|
|
229
326
|
onNotifyRef.current?.('error', 'callback_url 为空,无法提交')
|
|
230
327
|
return
|
|
231
328
|
}
|
|
232
|
-
|
|
233
|
-
runtime.reportData(answer)
|
|
329
|
+
reportSnapshot([])
|
|
234
330
|
runtime.reportEvent({
|
|
235
331
|
action: 'submit',
|
|
236
332
|
component: 'SubmitButton',
|
|
@@ -258,7 +354,7 @@ function AskUserFormCardBody({
|
|
|
258
354
|
onNotifyRef.current?.('error', agentError)
|
|
259
355
|
return
|
|
260
356
|
}
|
|
261
|
-
|
|
357
|
+
reportSnapshot([])
|
|
262
358
|
runtime.reportEvent({
|
|
263
359
|
action: 'submit',
|
|
264
360
|
component: 'SubmitButton',
|
|
@@ -281,6 +377,7 @@ function AskUserFormCardBody({
|
|
|
281
377
|
fieldNames,
|
|
282
378
|
renderBlocks,
|
|
283
379
|
extraValues,
|
|
380
|
+
reportSnapshot,
|
|
284
381
|
runtime,
|
|
285
382
|
submitMode,
|
|
286
383
|
callbackUrl,
|
|
@@ -407,7 +504,7 @@ export default function AskUserFormCard(props: AskUserFormCardProps) {
|
|
|
407
504
|
[],
|
|
408
505
|
)
|
|
409
506
|
|
|
410
|
-
// 有 URL + id
|
|
507
|
+
// 有 URL + id 即创建会话并挂到 Provider:表单刚可交互时 send 可入队,
|
|
411
508
|
// 不必等 setState(surfaceReady) / connect 完成。
|
|
412
509
|
const wsClient = useMemo(() => {
|
|
413
510
|
if (!realtimeWsUrl || !resolvedAskUserMessageId) return null
|
|
@@ -435,11 +532,13 @@ export default function AskUserFormCard(props: AskUserFormCardProps) {
|
|
|
435
532
|
return (
|
|
436
533
|
<SkoponFormProvider>
|
|
437
534
|
<FormRuntimeProvider client={wsClient}>
|
|
438
|
-
<
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
535
|
+
<FormLiveOptionsProvider>
|
|
536
|
+
<AskUserFormCardBody
|
|
537
|
+
{...props}
|
|
538
|
+
onSurfaceReadyChange={onSurfaceReadyChange}
|
|
539
|
+
onAutoControlReady={onAutoControlReady}
|
|
540
|
+
/>
|
|
541
|
+
</FormLiveOptionsProvider>
|
|
443
542
|
</FormRuntimeProvider>
|
|
444
543
|
</SkoponFormProvider>
|
|
445
544
|
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
import { buildFormRuntimeSnapshot } from './buildFormRuntimeSnapshot'
|
|
6
|
+
import type { FormBlock } from '../types/index'
|
|
7
|
+
|
|
8
|
+
describe('buildFormRuntimeSnapshot', () => {
|
|
9
|
+
const blocks: FormBlock[] = [
|
|
10
|
+
{
|
|
11
|
+
id: 'b1',
|
|
12
|
+
type: 'text',
|
|
13
|
+
name: 'title',
|
|
14
|
+
label: '标题',
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'b2',
|
|
19
|
+
type: 'select',
|
|
20
|
+
name: 'city',
|
|
21
|
+
label: '城市',
|
|
22
|
+
options: [
|
|
23
|
+
{ value: 'sh', label: '上海' },
|
|
24
|
+
{ value: 'bj', label: '北京' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'b3',
|
|
29
|
+
type: 'resume_multiselect',
|
|
30
|
+
name: 'resumes',
|
|
31
|
+
label: '简历',
|
|
32
|
+
options: [],
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
it('builds flat form_values and schema blocks', () => {
|
|
37
|
+
const snapshot = buildFormRuntimeSnapshot({
|
|
38
|
+
title: 'Ask',
|
|
39
|
+
description: 'desc',
|
|
40
|
+
blocks,
|
|
41
|
+
values: {
|
|
42
|
+
title: 'hello',
|
|
43
|
+
city: 'sh',
|
|
44
|
+
resumes: ['r1'],
|
|
45
|
+
unused: undefined,
|
|
46
|
+
},
|
|
47
|
+
formChange: [],
|
|
48
|
+
})
|
|
49
|
+
expect(snapshot.form_schema.title).toBe('Ask')
|
|
50
|
+
expect(snapshot.form_schema.description).toBe('desc')
|
|
51
|
+
expect(snapshot.form_schema.blocks).toHaveLength(3)
|
|
52
|
+
expect(snapshot.form_values).toEqual({
|
|
53
|
+
title: 'hello',
|
|
54
|
+
city: 'sh',
|
|
55
|
+
resumes: ['r1'],
|
|
56
|
+
})
|
|
57
|
+
expect(snapshot.form_change).toEqual([])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('overrides resume/case options from live registry', () => {
|
|
61
|
+
const live = new Map([
|
|
62
|
+
[
|
|
63
|
+
'resumes',
|
|
64
|
+
[
|
|
65
|
+
{ value: 'r1', label: 'Alice' },
|
|
66
|
+
{ value: 'r2', label: 'Bob' },
|
|
67
|
+
],
|
|
68
|
+
],
|
|
69
|
+
])
|
|
70
|
+
const snapshot = buildFormRuntimeSnapshot({
|
|
71
|
+
blocks,
|
|
72
|
+
values: { resumes: ['r1'] },
|
|
73
|
+
liveOptions: live,
|
|
74
|
+
formChange: ['resumes'],
|
|
75
|
+
})
|
|
76
|
+
const resumeBlock = snapshot.form_schema.blocks[2] as FormBlock
|
|
77
|
+
expect(resumeBlock.options).toEqual([
|
|
78
|
+
{ value: 'r1', label: 'Alice' },
|
|
79
|
+
{ value: 'r2', label: 'Bob' },
|
|
80
|
+
])
|
|
81
|
+
expect(snapshot.form_change).toEqual(['resumes'])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('does not mutate static select options via live map for other types', () => {
|
|
85
|
+
const live = new Map([['city', [{ value: 'gz', label: '广州' }]]])
|
|
86
|
+
const snapshot = buildFormRuntimeSnapshot({
|
|
87
|
+
blocks,
|
|
88
|
+
values: { city: 'sh' },
|
|
89
|
+
liveOptions: live,
|
|
90
|
+
})
|
|
91
|
+
const cityBlock = snapshot.form_schema.blocks[1] as FormBlock
|
|
92
|
+
expect(cityBlock.options).toEqual([
|
|
93
|
+
{ value: 'sh', label: '上海' },
|
|
94
|
+
{ value: 'bj', label: '北京' },
|
|
95
|
+
])
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FormBlock, FormBlockOption } from '../types/index'
|
|
2
|
+
import type { FormRuntimeDataPayload } from './types'
|
|
3
|
+
|
|
4
|
+
export type LiveOptionsMap = ReadonlyMap<string, FormBlockOption[]>
|
|
5
|
+
|
|
6
|
+
function omitUndefinedValues(
|
|
7
|
+
values: Record<string, unknown>,
|
|
8
|
+
): Record<string, unknown> {
|
|
9
|
+
const out: Record<string, unknown> = {}
|
|
10
|
+
for (const [key, value] of Object.entries(values)) {
|
|
11
|
+
if (value !== undefined) out[key] = value
|
|
12
|
+
}
|
|
13
|
+
return out
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function applyLiveOptions(
|
|
17
|
+
block: FormBlock,
|
|
18
|
+
liveOptions: LiveOptionsMap | undefined,
|
|
19
|
+
): FormBlock {
|
|
20
|
+
const name = block.name?.trim()
|
|
21
|
+
if (!name || !liveOptions) return { ...block }
|
|
22
|
+
const live = liveOptions.get(name)
|
|
23
|
+
if (!live) return { ...block }
|
|
24
|
+
if (block.type !== 'resume_multiselect' && block.type !== 'case_multiselect') {
|
|
25
|
+
return { ...block }
|
|
26
|
+
}
|
|
27
|
+
return { ...block, options: live.map((opt) => ({ ...opt })) }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BuildFormRuntimeSnapshotInput {
|
|
31
|
+
title?: string
|
|
32
|
+
description?: string
|
|
33
|
+
blocks: FormBlock[]
|
|
34
|
+
values: Record<string, unknown>
|
|
35
|
+
liveOptions?: LiveOptionsMap
|
|
36
|
+
formChange?: string[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 构建 form_runtime_data 快照:form_schema(block 格式)+ 扁平 form_values + form_change。
|
|
41
|
+
*/
|
|
42
|
+
export function buildFormRuntimeSnapshot(
|
|
43
|
+
input: BuildFormRuntimeSnapshotInput,
|
|
44
|
+
): FormRuntimeDataPayload {
|
|
45
|
+
const blocks = (input.blocks ?? []).map((block) =>
|
|
46
|
+
applyLiveOptions(block, input.liveOptions),
|
|
47
|
+
)
|
|
48
|
+
const form_schema: FormRuntimeDataPayload['form_schema'] = {
|
|
49
|
+
blocks,
|
|
50
|
+
}
|
|
51
|
+
const title = input.title?.trim()
|
|
52
|
+
const description = input.description?.trim()
|
|
53
|
+
if (title) form_schema.title = title
|
|
54
|
+
if (description) form_schema.description = description
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
form_schema,
|
|
58
|
+
form_values: omitUndefinedValues(input.values ?? {}),
|
|
59
|
+
form_change: [...(input.formChange ?? [])],
|
|
60
|
+
}
|
|
61
|
+
}
|