@skopon-cool/form-sdk 0.4.8 → 0.5.0
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/README.md +26 -1
- package/dist/adapter/formSchema.d.ts.map +1 -1
- package/dist/adapter/formWeb.d.ts +14 -0
- package/dist/adapter/formWeb.d.ts.map +1 -0
- package/dist/blocks/registry.d.ts.map +1 -1
- package/dist/blocks/web/adapter.d.ts +8 -0
- package/dist/blocks/web/adapter.d.ts.map +1 -0
- package/dist/blocks/web/catalog.d.ts +9 -0
- package/dist/blocks/web/catalog.d.ts.map +1 -0
- package/dist/blocks/web/index.d.ts +3 -0
- package/dist/blocks/web/index.d.ts.map +1 -0
- package/dist/client/authStorage.d.ts +1 -1
- package/dist/client/authStorage.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/form-sdk.css +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1364 -1182
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts +2 -2
- package/dist/submit/intersectPayloadBlocksWithForm.d.ts.map +1 -1
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapter/a2uiAdapter.test.ts +39 -0
- package/src/adapter/formSchema.test.ts +38 -0
- package/src/adapter/formSchema.ts +16 -1
- package/src/adapter/formWeb.test.ts +38 -0
- package/src/adapter/formWeb.ts +41 -0
- package/src/blocks/registry.ts +6 -1
- package/src/blocks/web/adapter.ts +36 -0
- package/src/blocks/web/catalog.test.tsx +95 -0
- package/src/blocks/web/catalog.tsx +158 -0
- package/src/blocks/web/index.ts +11 -0
- package/src/client/authStorage.ts +1 -1
- package/src/components/AskUserFormCard.tsx +2 -1
- package/src/index.ts +9 -0
- package/src/styles/index.css +87 -0
- package/src/submit/intersectPayloadBlocksWithForm.ts +6 -5
- package/src/submit/submit.test.ts +14 -0
- package/src/submit/validateFormSubmit.ts +2 -2
- package/src/types/index.ts +12 -2
package/src/index.ts
CHANGED
|
@@ -27,9 +27,18 @@ export {
|
|
|
27
27
|
isLayoutBlockType,
|
|
28
28
|
isMediaBlockType,
|
|
29
29
|
isOptionsBlockType,
|
|
30
|
+
isWebBlockType,
|
|
30
31
|
supportsDefaultValue,
|
|
31
32
|
} from './types/index'
|
|
32
33
|
|
|
34
|
+
export {
|
|
35
|
+
FORM_WEB_DEFAULT_HEIGHT,
|
|
36
|
+
FORM_WEB_MAX_HEIGHT,
|
|
37
|
+
FORM_WEB_MIN_HEIGHT,
|
|
38
|
+
isSupportedWebUrl,
|
|
39
|
+
normalizeWebHeight,
|
|
40
|
+
} from './adapter/formWeb'
|
|
41
|
+
|
|
33
42
|
export {
|
|
34
43
|
a2uiToBlocks,
|
|
35
44
|
blocksToA2ui,
|
package/src/styles/index.css
CHANGED
|
@@ -105,6 +105,93 @@
|
|
|
105
105
|
text-align: center;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/* 网页展示 */
|
|
109
|
+
.skopon-web-preview {
|
|
110
|
+
min-width: 0;
|
|
111
|
+
max-width: 100%;
|
|
112
|
+
overflow-x: hidden;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.skopon-web-frame-shell {
|
|
116
|
+
position: relative;
|
|
117
|
+
box-sizing: border-box;
|
|
118
|
+
width: 100%;
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
min-width: 0;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
border: 1px solid var(--color-border-subtle);
|
|
123
|
+
border-radius: var(--radius-md);
|
|
124
|
+
background: var(--color-bg-subtle);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.skopon-web-frame-shell--empty {
|
|
128
|
+
border-style: dashed;
|
|
129
|
+
border-color: var(--color-border);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.skopon-web-frame {
|
|
133
|
+
display: block;
|
|
134
|
+
box-sizing: border-box;
|
|
135
|
+
width: 100%;
|
|
136
|
+
max-width: 100%;
|
|
137
|
+
min-width: 0;
|
|
138
|
+
border: 0;
|
|
139
|
+
background: var(--color-surface);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.skopon-web-empty {
|
|
143
|
+
display: flex;
|
|
144
|
+
width: 100%;
|
|
145
|
+
height: 100%;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
padding: var(--space-4);
|
|
149
|
+
color: var(--color-text-muted);
|
|
150
|
+
font-size: var(--font-size-sm);
|
|
151
|
+
text-align: center;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.skopon-web-expand-button.ant-btn {
|
|
155
|
+
position: absolute;
|
|
156
|
+
right: var(--space-3);
|
|
157
|
+
bottom: var(--space-3);
|
|
158
|
+
z-index: 1;
|
|
159
|
+
width: var(--button-height-md);
|
|
160
|
+
min-width: var(--button-height-md);
|
|
161
|
+
height: var(--button-height-md);
|
|
162
|
+
padding: 0;
|
|
163
|
+
color: var(--color-primary);
|
|
164
|
+
border-color: var(--color-border);
|
|
165
|
+
background: color-mix(in srgb, var(--color-surface) 92%, transparent);
|
|
166
|
+
box-shadow: var(--shadow-md);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.skopon-web-expand-button.ant-btn:hover {
|
|
170
|
+
color: var(--color-primary-hover);
|
|
171
|
+
border-color: var(--color-primary-hover);
|
|
172
|
+
background: var(--color-surface);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.skopon-web-expand-button.ant-btn:focus-visible {
|
|
176
|
+
outline: none;
|
|
177
|
+
box-shadow: var(--shadow-focus), var(--shadow-md);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.skopon-web-modal .ant-modal-content {
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
border-radius: var(--radius-lg);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.skopon-web-modal .ant-modal-body {
|
|
186
|
+
padding: 0;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.skopon-web-frame--modal {
|
|
191
|
+
height: min(80vh, 900px);
|
|
192
|
+
height: min(80dvh, 900px);
|
|
193
|
+
}
|
|
194
|
+
|
|
108
195
|
.form-file-upload-preview {
|
|
109
196
|
margin-top: var(--space-1);
|
|
110
197
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeFormDefinition } from '../adapter/formSchema'
|
|
2
2
|
import type { FormBlock, FormSchema } from '../types/index'
|
|
3
|
-
import {
|
|
3
|
+
import { isDisplayBlockType, isInputBlockType } from '../types/index'
|
|
4
4
|
|
|
5
5
|
export type PayloadBlocksDefinition = Pick<FormSchema, 'title' | 'description' | 'blocks'> & {
|
|
6
6
|
/**
|
|
@@ -13,7 +13,7 @@ export type PayloadBlocksDefinition = Pick<FormSchema, 'title' | 'description' |
|
|
|
13
13
|
export interface PayloadBlocksIntersection {
|
|
14
14
|
matchedBlocks: FormBlock[]
|
|
15
15
|
extraBlocks: FormBlock[]
|
|
16
|
-
/** 按 payload 顺序:layout + matched input(不含 extra input) */
|
|
16
|
+
/** 按 payload 顺序:layout/media + matched input(不含 extra input) */
|
|
17
17
|
renderBlocks: FormBlock[]
|
|
18
18
|
title: string
|
|
19
19
|
description: string
|
|
@@ -46,7 +46,8 @@ function buildRenderBlocks(
|
|
|
46
46
|
|
|
47
47
|
const renderBlocks: FormBlock[] = []
|
|
48
48
|
for (const payloadBlock of payloadDef.blocks) {
|
|
49
|
-
|
|
49
|
+
// layout / media 按 payload 原样上屏(ask 动态 mediaUrls 必须保留)
|
|
50
|
+
if (isDisplayBlockType(payloadBlock.type)) {
|
|
50
51
|
renderBlocks.push(normalizePayloadBlock(payloadBlock))
|
|
51
52
|
continue
|
|
52
53
|
}
|
|
@@ -205,10 +206,10 @@ export function payloadHasInputBlocks(payloadDef: PayloadBlocksDefinition): bool
|
|
|
205
206
|
)
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
/** 交集为空时 fallback:payload 中可上屏的 blocks(layout + 非 hidden input) */
|
|
209
|
+
/** 交集为空时 fallback:payload 中可上屏的 blocks(layout/media + 非 hidden input) */
|
|
209
210
|
export function getPayloadRenderableBlocks(payloadDef: PayloadBlocksDefinition): FormBlock[] {
|
|
210
211
|
return payloadDef.blocks.filter((block) => {
|
|
211
|
-
if (
|
|
212
|
+
if (isDisplayBlockType(block.type)) return true
|
|
212
213
|
if (!isInputBlockType(block.type) || !block.name?.trim()) return false
|
|
213
214
|
// hidden text 不上屏;提交值由 getPayloadHiddenBlockValues / dataModel 补齐
|
|
214
215
|
return !isHiddenTextBlock(block)
|
|
@@ -734,6 +734,20 @@ describe('submitFormJson', () => {
|
|
|
734
734
|
})
|
|
735
735
|
|
|
736
736
|
describe('validateFormSubmitValues', () => {
|
|
737
|
+
it('ignores display blocks even if they contain input-like metadata', () => {
|
|
738
|
+
const blocks = [
|
|
739
|
+
{
|
|
740
|
+
id: 'web-1',
|
|
741
|
+
type: 'web' as const,
|
|
742
|
+
name: 'embedded_page',
|
|
743
|
+
label: '网页',
|
|
744
|
+
required: true,
|
|
745
|
+
webUrl: 'https://example.com',
|
|
746
|
+
},
|
|
747
|
+
]
|
|
748
|
+
expect(validateFormSubmitValues(blocks, {})).toBeNull()
|
|
749
|
+
})
|
|
750
|
+
|
|
737
751
|
it('rejects empty required text field', () => {
|
|
738
752
|
const blocks = [
|
|
739
753
|
{ id: 'b1', type: 'text' as const, name: 'title', label: '标题', required: true },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormBlock } from '../types/index'
|
|
2
|
-
import {
|
|
2
|
+
import { isInputBlockType } from '../types/index'
|
|
3
3
|
import { EMAIL_REGEXP } from '../adapter/a2uiAdapter'
|
|
4
4
|
import { coerceToggleValue } from '../adapter/formSchema'
|
|
5
5
|
import { asStringArray } from '../catalog/useSkoponBoundField'
|
|
@@ -52,9 +52,9 @@ export function validateFormSubmitValues(
|
|
|
52
52
|
values: Record<string, unknown>,
|
|
53
53
|
): FormSubmitValidationError | null {
|
|
54
54
|
for (const block of blocks) {
|
|
55
|
+
if (!isInputBlockType(block.type)) continue
|
|
55
56
|
const name = block.name?.trim()
|
|
56
57
|
if (!name) continue
|
|
57
|
-
if (isLayoutBlockType(block.type) || isMediaBlockType(block.type)) continue
|
|
58
58
|
|
|
59
59
|
const raw = values[name]
|
|
60
60
|
const label = block.label?.trim() || name
|
package/src/types/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type A2uiComponentName =
|
|
|
16
16
|
| 'DateTimeInput'
|
|
17
17
|
| 'FileUpload'
|
|
18
18
|
| 'SkoponMedia'
|
|
19
|
+
| 'SkoponWeb'
|
|
19
20
|
| 'SkoponSelect'
|
|
20
21
|
| 'SkoponResumeSelect'
|
|
21
22
|
| 'SkoponCaseSelect'
|
|
@@ -62,6 +63,7 @@ export type FormBlockType =
|
|
|
62
63
|
| 'image'
|
|
63
64
|
| 'video'
|
|
64
65
|
| 'audio'
|
|
66
|
+
| 'web'
|
|
65
67
|
|
|
66
68
|
export type FormMediaSize = 'huge' | 'large' | 'medium' | 'small' | 'icon'
|
|
67
69
|
|
|
@@ -131,6 +133,10 @@ export interface FormBlock {
|
|
|
131
133
|
mediaUrl?: string
|
|
132
134
|
mediaUrls?: string[]
|
|
133
135
|
mediaSize?: FormMediaSize
|
|
136
|
+
/** 网页展示块:iframe 地址,仅支持 HTTP(S) 或同源相对地址 */
|
|
137
|
+
webUrl?: string
|
|
138
|
+
/** 网页展示块:行内 iframe 高度(px) */
|
|
139
|
+
webHeight?: number
|
|
134
140
|
fileAcceptTypes?: string[]
|
|
135
141
|
fileAcceptExtensions?: string[]
|
|
136
142
|
filePlaceholderIcon?: FormFilePlaceholderIcon
|
|
@@ -173,16 +179,20 @@ export function isMediaBlockType(type: FormBlockType): boolean {
|
|
|
173
179
|
return type === 'image' || type === 'video' || type === 'audio'
|
|
174
180
|
}
|
|
175
181
|
|
|
182
|
+
export function isWebBlockType(type: FormBlockType): boolean {
|
|
183
|
+
return type === 'web'
|
|
184
|
+
}
|
|
185
|
+
|
|
176
186
|
export function isLayoutBlockType(type: FormBlockType): boolean {
|
|
177
187
|
return type === 'heading' || type === 'paragraph'
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
export function isInputBlockType(type: FormBlockType): boolean {
|
|
181
|
-
return !isLayoutBlockType(type) && !isMediaBlockType(type)
|
|
191
|
+
return !isLayoutBlockType(type) && !isMediaBlockType(type) && !isWebBlockType(type)
|
|
182
192
|
}
|
|
183
193
|
|
|
184
194
|
export function isDisplayBlockType(type: FormBlockType): boolean {
|
|
185
|
-
return isLayoutBlockType(type) || isMediaBlockType(type)
|
|
195
|
+
return isLayoutBlockType(type) || isMediaBlockType(type) || isWebBlockType(type)
|
|
186
196
|
}
|
|
187
197
|
|
|
188
198
|
export function isOptionsBlockType(type: FormBlockType): boolean {
|