@svadmin/ui 0.88.0 → 0.91.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.
Files changed (171) hide show
  1. package/dist/app.css +4 -0
  2. package/dist/app.theme.css +4 -0
  3. package/dist/component-registry.svelte.d.ts +2 -2
  4. package/dist/components/AboutSettings.svelte +23 -4
  5. package/dist/components/AdminApp.svelte +6 -4
  6. package/dist/components/AdminApp.svelte.d.ts +3 -3
  7. package/dist/components/ApiSettings.svelte +43 -27
  8. package/dist/components/ApprovalActionCard.svelte +8 -9
  9. package/dist/components/ApprovalCenter.svelte +24 -5
  10. package/dist/components/AuditLogViewer.svelte +2 -2
  11. package/dist/components/AutoComplete.svelte +610 -0
  12. package/dist/components/AutoComplete.svelte.d.ts +25 -0
  13. package/dist/components/AutoForm.svelte +263 -18
  14. package/dist/components/AutoForm.svelte.d.ts +19 -2
  15. package/dist/components/AutoTable.svelte +135 -35
  16. package/dist/components/AutoTable.svelte.d.ts +8 -0
  17. package/dist/components/BatchActionBar.svelte +42 -14
  18. package/dist/components/BatchActionBar.svelte.d.ts +5 -0
  19. package/dist/components/BoundRecordDetailDrawer.svelte +5 -3
  20. package/dist/components/CancelTaskButton.svelte +14 -1
  21. package/dist/components/Cascader.svelte +412 -53
  22. package/dist/components/Cascader.svelte.d.ts +7 -1
  23. package/dist/components/ColorPicker.svelte +1 -1
  24. package/dist/components/ColorPicker.svelte.d.ts +1 -1
  25. package/dist/components/ComboboxField.svelte +270 -34
  26. package/dist/components/ComboboxField.svelte.d.ts +4 -0
  27. package/dist/components/CommandPalette.svelte +4 -4
  28. package/dist/components/DashboardView.svelte +73 -7
  29. package/dist/components/DashboardView.svelte.d.ts +3 -1
  30. package/dist/components/DateRangeInput.svelte +8 -1
  31. package/dist/components/DetailWorkspaceAsyncSection.svelte +107 -0
  32. package/dist/components/DetailWorkspaceAsyncSection.svelte.d.ts +36 -0
  33. package/dist/components/DetailWorkspaceSections.svelte +233 -0
  34. package/dist/components/DetailWorkspaceSections.svelte.d.ts +9 -0
  35. package/dist/components/DrawerForm.svelte +78 -9
  36. package/dist/components/DrawerForm.svelte.d.ts +3 -3
  37. package/dist/components/ErrorBoundary.svelte +1 -1
  38. package/dist/components/ErrorComponent.svelte +19 -6
  39. package/dist/components/ErrorComponent.svelte.d.ts +1 -0
  40. package/dist/components/ErrorSummary.svelte +9 -4
  41. package/dist/components/FieldRenderer.svelte +203 -90
  42. package/dist/components/FieldRenderer.svelte.d.ts +16 -1
  43. package/dist/components/FileUpload.svelte +116 -51
  44. package/dist/components/FileUpload.svelte.d.ts +7 -27
  45. package/dist/components/FileUploadField.svelte +170 -0
  46. package/dist/components/FileUploadField.svelte.d.ts +21 -0
  47. package/dist/components/FilterBuilder.svelte +13 -7
  48. package/dist/components/FilterCollectionInput.svelte +9 -7
  49. package/dist/components/FilterCollectionInput.svelte.d.ts +3 -0
  50. package/dist/components/ForgotPasswordPage.svelte +1 -1
  51. package/dist/components/Header.svelte +1 -1
  52. package/dist/components/ImportWizard.svelte +197 -28
  53. package/dist/components/ImportWizard.svelte.d.ts +5 -1
  54. package/dist/components/InferencerPanel.svelte +4 -2
  55. package/dist/components/InfiniteList.svelte +52 -9
  56. package/dist/components/JsonSchemaForm.svelte +237 -42
  57. package/dist/components/JsonSchemaForm.svelte.d.ts +10 -3
  58. package/dist/components/KanbanBoard.svelte +43 -7
  59. package/dist/components/KanbanBoard.svelte.d.ts +2 -2
  60. package/dist/components/Layout.svelte +1 -1
  61. package/dist/components/LoginPage.svelte +1 -1
  62. package/dist/components/MetricStrip.svelte +8 -3
  63. package/dist/components/MetricStrip.svelte.d.ts +1 -0
  64. package/dist/components/ModalForm.svelte +85 -9
  65. package/dist/components/ModalForm.svelte.d.ts +3 -3
  66. package/dist/components/MoneyInput.svelte +5 -3
  67. package/dist/components/MoneyInput.svelte.d.ts +4 -2
  68. package/dist/components/MultiTabKeepAlive.svelte +502 -43
  69. package/dist/components/MultiTabKeepAlive.svelte.d.ts +40 -6
  70. package/dist/components/NotificationsSettings.svelte +10 -10
  71. package/dist/components/NumberInput.svelte +5 -3
  72. package/dist/components/NumberInput.svelte.d.ts +4 -2
  73. package/dist/components/NumericInput.svelte +277 -25
  74. package/dist/components/NumericInput.svelte.d.ts +3 -1
  75. package/dist/components/PercentInput.svelte +5 -3
  76. package/dist/components/PercentInput.svelte.d.ts +4 -2
  77. package/dist/components/PermissionMatrix.svelte +44 -8
  78. package/dist/components/PermissionMatrix.svelte.d.ts +1 -0
  79. package/dist/components/ProfilePage.svelte +38 -13
  80. package/dist/components/RadioGroup.svelte +203 -4
  81. package/dist/components/RadioGroup.svelte.d.ts +1 -1
  82. package/dist/components/Rate.svelte +3 -3
  83. package/dist/components/Rate.svelte.d.ts +2 -2
  84. package/dist/components/RecordDetailDrawer.svelte +1 -1
  85. package/dist/components/RecordDetailDrawer.svelte.d.ts +1 -1
  86. package/dist/components/RegisterPage.svelte +1 -1
  87. package/dist/components/RelationPicker.svelte +39 -11
  88. package/dist/components/ResourceAccessGuard.svelte +7 -2
  89. package/dist/components/RetryTaskButton.svelte +14 -1
  90. package/dist/components/RolesSettings.svelte +74 -28
  91. package/dist/components/SecuritySettings.svelte +66 -51
  92. package/dist/components/SegmentedControl.svelte +4 -2
  93. package/dist/components/SegmentedControl.svelte.d.ts +1 -1
  94. package/dist/components/Sidebar.svelte +1 -1
  95. package/dist/components/StepsForm.svelte +60 -10
  96. package/dist/components/StepsForm.svelte.d.ts +4 -2
  97. package/dist/components/SvadminLogo.svelte +1 -0
  98. package/dist/components/SvarDataGrid.svelte +42 -24
  99. package/dist/components/SvarDataGrid.svelte.d.ts +10 -10
  100. package/dist/components/SvarResourceRead.svelte +2 -0
  101. package/dist/components/SvarResourceTableView.svelte +4 -2
  102. package/dist/components/TagsInput.svelte +3 -3
  103. package/dist/components/TagsInput.svelte.d.ts +2 -2
  104. package/dist/components/TaskDetails.svelte +22 -16
  105. package/dist/components/TaskDetails.svelte.d.ts +1 -1
  106. package/dist/components/TaskList.svelte +57 -15
  107. package/dist/components/TaskQueueDrawer.svelte +54 -34
  108. package/dist/components/Transfer.svelte +67 -5
  109. package/dist/components/Transfer.svelte.d.ts +4 -0
  110. package/dist/components/TreeSelect.svelte +337 -77
  111. package/dist/components/TreeSelect.svelte.d.ts +4 -0
  112. package/dist/components/TreeTable.svelte +6 -1
  113. package/dist/components/TwoFactorAuthPage.svelte +8 -4
  114. package/dist/components/UpdatePasswordPage.svelte +1 -1
  115. package/dist/components/VersionDiffViewer.svelte +9 -7
  116. package/dist/components/account/CompanyProfilePage.svelte +13 -12
  117. package/dist/components/account/MemberDirectory.svelte +14 -10
  118. package/dist/components/account/SettingsEnterprisePage.svelte +18 -18
  119. package/dist/components/content/AuditTimeline.svelte +8 -6
  120. package/dist/components/content/DataState.svelte +1 -1
  121. package/dist/components/content/FileList.svelte +44 -13
  122. package/dist/components/content/FileList.svelte.d.ts +4 -2
  123. package/dist/components/content/FileListRow.svelte +87 -0
  124. package/dist/components/content/FileListRow.svelte.d.ts +11 -0
  125. package/dist/components/content/ImportDropzone.svelte +109 -12
  126. package/dist/components/content/ImportDropzone.svelte.d.ts +1 -0
  127. package/dist/components/content/IntegrationCard.svelte +3 -3
  128. package/dist/components/content/IntegrationCard.svelte.d.ts +2 -0
  129. package/dist/components/content/MediaThumbnail.svelte +6 -2
  130. package/dist/components/detail-workspace.d.ts +60 -0
  131. package/dist/components/detail-workspace.js +3 -0
  132. package/dist/components/enterprise/json-schema-form.d.ts +1 -1
  133. package/dist/components/enterprise/recursive-schema-form.d.ts +15 -1
  134. package/dist/components/enterprise/recursive-schema-form.js +288 -14
  135. package/dist/components/enterprise/schema-form-references.d.ts +14 -0
  136. package/dist/components/enterprise/schema-form-references.js +316 -0
  137. package/dist/components/fields/ArrayField.svelte +178 -14
  138. package/dist/components/fields/ArrayField.svelte.d.ts +17 -3
  139. package/dist/components/fields/array-field-metadata.d.ts +4 -0
  140. package/dist/components/fields/array-field-metadata.js +36 -0
  141. package/dist/components/file-upload-reference.d.ts +11 -0
  142. package/dist/components/file-upload-reference.js +50 -0
  143. package/dist/components/file-upload-types.d.ts +32 -0
  144. package/dist/components/file-upload-types.js +1 -0
  145. package/dist/components/form-contract.upload-type-fixture.svelte +108 -0
  146. package/dist/components/form-contract.upload-type-fixture.svelte.d.ts +18 -0
  147. package/dist/components/json-schema-form-state.d.ts +19 -0
  148. package/dist/components/list-deletion.svelte.js +6 -1
  149. package/dist/components/list-state.svelte.d.ts +12 -1
  150. package/dist/components/list-state.svelte.js +56 -16
  151. package/dist/components/numeric-format.d.ts +44 -0
  152. package/dist/components/numeric-format.js +198 -0
  153. package/dist/components/record-detail.svelte.js +2 -0
  154. package/dist/components/saved-list-views.d.ts +9 -2
  155. package/dist/components/saved-list-views.js +42 -11
  156. package/dist/components/saved-list-views.svelte.d.ts +3 -0
  157. package/dist/components/saved-list-views.svelte.js +56 -7
  158. package/dist/components/task-action.svelte.d.ts +1 -0
  159. package/dist/components/task-action.svelte.js +55 -3
  160. package/dist/components/task-utils.d.ts +1 -0
  161. package/dist/components/task-utils.js +18 -0
  162. package/dist/components/ui/input/input.svelte +1 -1
  163. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  164. package/dist/components/ui/sheet/sheet.svelte +4 -1
  165. package/dist/components/version-history.d.ts +14 -0
  166. package/dist/components/version-history.js +32 -0
  167. package/dist/components/workspace-tab-state.d.ts +78 -0
  168. package/dist/components/workspace-tab-state.js +587 -0
  169. package/dist/index.d.ts +13 -1
  170. package/dist/index.js +4 -0
  171. package/package.json +5 -5
@@ -24,6 +24,8 @@
24
24
  import DateTimeInput from './DateTimeInput.svelte';
25
25
  import DateRangeInput, { type DateRangeInputValue } from './DateRangeInput.svelte';
26
26
  import FileUpload from './FileUpload.svelte';
27
+ import type { UploadCancelHandler, UploadHandler } from './file-upload-types.js';
28
+ import FileUploadField from './FileUploadField.svelte';
27
29
  import ColorPicker from './ColorPicker.svelte';
28
30
  import Rate from './Rate.svelte';
29
31
  import TagsInput from './TagsInput.svelte';
@@ -36,23 +38,51 @@
36
38
  import type { Snippet } from 'svelte';
37
39
  import { getRichTextEditor } from '../editor-config.svelte.js';
38
40
  import { numericInputValue } from '../numeric-input.js';
41
+ import type { NumericInputLocale } from './numeric-format.js';
39
42
 
40
43
  type FieldOption = NonNullable<FieldDefinition['options']>[number];
41
44
  const i18n = useTranslation();
42
45
 
43
- let { field, value, onchange, disabled, invalid = false, errorId, children } = $props<{
46
+ let {
47
+ field, value, onchange, disabled, invalid = false, errorId, id, children, nativeSubmit = true,
48
+ upload, cancelUpload, onUploadStateChange, fileReferenceMode = false, fileScopeKey,
49
+ } = $props<{
44
50
  field: FieldDefinition;
45
51
  value: unknown;
46
52
  onchange: (val: unknown) => void;
47
53
  disabled?: boolean;
48
54
  invalid?: boolean;
49
- errorId?: string;
55
+ errorId?: string | undefined;
56
+ id?: string;
50
57
  children?: Snippet;
58
+ /** 嵌套数组的可序列化值由外层 JSON 提交;原始文件保留原生通道。 */
59
+ nativeSubmit?: boolean;
60
+ /** 开启异步上传后,字段值只保存可序列化引用,不再接收 File。 */
61
+ upload?: UploadHandler;
62
+ cancelUpload?: UploadCancelHandler;
63
+ onUploadStateChange?: (state: { busy: boolean; unresolved: boolean }) => void;
64
+ /** AutoForm 的 JSON 文件模式;独立 FieldRenderer 默认保留原生 multipart。 */
65
+ fileReferenceMode?: boolean;
66
+ /** 记录/上传作用域或 reset 变化时只重挂载文件字段。 */
67
+ fileScopeKey?: string | number | object;
51
68
  }>();
52
69
 
70
+ const instanceId = $props.id();
71
+ const controlId = $derived(id ?? (children ? field.key : `${instanceId}-control`));
72
+ const labelId = $derived(children ? `label-${controlId}` : `${instanceId}-label`);
73
+ const labelTarget = $derived(!children && field.type === 'daterange' ? `${controlId}-start` : controlId);
74
+ const descriptionId = $derived(`${instanceId}-description`);
75
+ const describedBy = $derived([field.description ? descriptionId : undefined, errorId].filter(Boolean).join(' ') || undefined);
76
+ const nativeFieldName = $derived(nativeSubmit ? field.key : undefined);
77
+ const nativeArrayName = $derived(nativeSubmit ? `${field.key}[]` : undefined);
78
+
53
79
  // Typed accessors
54
80
  const strVal = $derived((value as string) ?? '');
55
81
  const numVal = $derived(numericInputValue(value));
82
+ // FieldDefinition.locale is intentionally a runtime string. Pass it through so
83
+ // NumericInput can render an explicit unsupported-locale state instead of
84
+ // silently switching the field back to the default number path.
85
+ const numericLocale = $derived(field.locale as NumericInputLocale | undefined);
56
86
  const boolVal = $derived((value as boolean) ?? false);
57
87
  const tagsVal = $derived((value as string[]) ?? []);
58
88
  const multiVal = $derived((value as (string | number)[]) ?? []);
@@ -90,18 +120,21 @@
90
120
  }
91
121
  }
92
122
 
93
- // Images management
123
+ // Images management:禁用态保留回显,但所有入口都不能改值。
94
124
  function addImage() {
125
+ if (disabled) return;
95
126
  onchange([...imagesVal, '']);
96
127
  }
97
128
 
98
129
  function updateImage(index: number, url: string) {
130
+ if (disabled) return;
99
131
  const next = [...imagesVal];
100
132
  next[index] = url;
101
133
  onchange(next);
102
134
  }
103
135
 
104
136
  function removeImage(index: number) {
137
+ if (disabled) return;
105
138
  onchange(imagesVal.filter((_: string, i: number) => i !== index));
106
139
  }
107
140
  </script>
@@ -111,162 +144,182 @@
111
144
  data-svadmin-field
112
145
  data-svadmin-field-key={field.key}
113
146
  >
114
- <Label for={field.key} id="label-{field.key}" data-svadmin-field-label>
147
+ <Label {...definedOptions({ for: !children && (['array', 'transfer'].includes(field.type) || field.type === 'richtext' && getRichTextEditor()) ? undefined : labelTarget })} id={labelId} data-svadmin-field-label>
115
148
  {field.label}
116
149
  {#if field.required}
117
150
  <span class="svadmin-u-811148b13d1e">*</span>
118
151
  {/if}
119
152
  </Label>
153
+ {#if field.description}
154
+ <p id={descriptionId} data-svadmin-field-description class="svadmin-u-359090c2d529 svadmin-u-bfa603190748">{field.description}</p>
155
+ {/if}
120
156
 
121
157
  {#if children}
122
158
  {@render children()}
123
159
 
124
160
  {:else if field.type === 'array'}
125
- <input type="hidden" name={field.key} value={JSON.stringify(Array.isArray(value) ? value : [])} />
126
- <ArrayField {field} {value} {onchange} />
161
+ {#if nativeSubmit}
162
+ <input type="hidden" name={field.key} value={JSON.stringify(Array.isArray(value) ? value : [])} {disabled} />
163
+ {/if}
164
+ <ArrayField
165
+ {field}
166
+ {value}
167
+ {onchange}
168
+ {...definedOptions({ disabled })}
169
+ labelledby={labelId}
170
+ {...definedOptions({ describedby: describedBy })}
171
+ invalid={invalid}
172
+ nativeSubmit={false}
173
+ {fileReferenceMode}
174
+ {...definedOptions({ upload, cancelUpload, onUploadStateChange, fileScopeKey })}
175
+ />
127
176
 
128
177
  {:else if field.type === 'text' || field.type === 'image'}
129
178
  <Input
130
- id={field.key}
131
- name={field.key}
179
+ id={controlId}
180
+ name={nativeFieldName}
132
181
  type="text"
133
182
  value={strVal}
134
183
  oninput={(e) => onchange((e.target as HTMLInputElement).value)}
135
184
  required={field.required}
136
185
  aria-invalid={invalid || undefined}
137
- aria-describedby={errorId}
186
+ aria-describedby={describedBy}
138
187
  {disabled}
139
188
  placeholder={i18n.t('field.enterValue', { label: field.label })}
140
189
  />
141
190
 
142
191
  {:else if field.type === 'email'}
143
192
  <Input
144
- id={field.key}
145
- name={field.key}
193
+ id={controlId}
194
+ name={nativeFieldName}
146
195
  type="email"
147
196
  value={strVal}
148
197
  oninput={(e) => onchange((e.target as HTMLInputElement).value)}
149
198
  required={field.required}
150
199
  aria-invalid={invalid || undefined}
151
- aria-describedby={errorId}
200
+ aria-describedby={describedBy}
152
201
  {disabled}
153
202
  placeholder="name@example.com"
154
203
  />
155
204
 
156
205
  {:else if field.type === 'url'}
157
206
  <Input
158
- id={field.key}
159
- name={field.key}
207
+ id={controlId}
208
+ name={nativeFieldName}
160
209
  type="url"
161
210
  value={strVal}
162
211
  oninput={(e) => onchange((e.target as HTMLInputElement).value)}
163
212
  required={field.required}
164
213
  aria-invalid={invalid || undefined}
165
- aria-describedby={errorId}
214
+ aria-describedby={describedBy}
166
215
  {disabled}
167
216
  placeholder="https://"
168
217
  />
169
218
 
170
219
  {:else if field.type === 'phone'}
171
220
  <Input
172
- id={field.key}
173
- name={field.key}
221
+ id={controlId}
222
+ name={nativeFieldName}
174
223
  type="tel"
175
224
  value={strVal}
176
225
  oninput={(e) => onchange((e.target as HTMLInputElement).value)}
177
226
  required={field.required}
178
227
  aria-invalid={invalid || undefined}
179
- aria-describedby={errorId}
228
+ aria-describedby={describedBy}
180
229
  {disabled}
181
230
  placeholder="+1 (555) 000-0000"
182
231
  />
183
232
 
184
233
  {:else if field.type === 'color'}
185
234
  <ColorPicker
186
- id={field.key}
187
- name={field.key}
235
+ id={controlId}
188
236
  value={strVal}
189
237
  onchange={onchange}
190
238
  required={field.required}
191
239
  invalid={invalid}
192
- describedby={errorId}
193
240
  ariaLabel={field.label}
194
241
  {disabled}
242
+ {...definedOptions({ name: nativeFieldName, describedby: describedBy })}
195
243
  />
196
244
 
197
245
  {:else if field.type === 'number'}
198
246
  <NumberInput
199
- id={field.key}
200
- name={field.key}
247
+ id={controlId}
248
+ name={nativeFieldName}
201
249
  value={numVal}
202
250
  min={field.min}
203
251
  max={field.max}
204
252
  step={field.step}
205
253
  precision={field.precision}
254
+ locale={numericLocale}
206
255
  onchange={onchange}
207
256
  required={field.required}
208
257
  invalid={invalid}
209
- describedby={errorId}
258
+ describedby={describedBy}
210
259
  {disabled}
211
260
  />
212
261
 
213
262
  {:else if field.type === 'currency'}
214
263
  <MoneyInput
215
- id={field.key}
216
- name={field.key}
264
+ id={controlId}
265
+ name={nativeFieldName}
217
266
  value={numVal}
218
267
  currency={field.currency ?? 'USD'}
219
268
  min={field.min}
220
269
  max={field.max}
221
270
  step={field.step}
222
271
  precision={field.precision ?? 2}
272
+ locale={numericLocale}
223
273
  onchange={onchange}
224
274
  required={field.required}
225
275
  invalid={invalid}
226
- describedby={errorId}
276
+ describedby={describedBy}
227
277
  {disabled}
228
278
  />
229
279
 
230
280
  {:else if field.type === 'percent'}
231
281
  <PercentInput
232
- id={field.key}
233
- name={field.key}
282
+ id={controlId}
283
+ name={nativeFieldName}
234
284
  value={numVal}
235
285
  scale={field.scale ?? '100'}
236
286
  min={field.min}
237
287
  max={field.max}
238
288
  step={field.step}
239
289
  precision={field.precision ?? 1}
290
+ locale={numericLocale}
240
291
  onchange={onchange}
241
292
  required={field.required}
242
293
  invalid={invalid}
243
- describedby={errorId}
294
+ describedby={describedBy}
244
295
  {disabled}
245
296
  />
246
297
 
247
298
  {:else if field.type === 'richtext'}
248
299
  {@const EditorComp = getRichTextEditor()}
249
300
  {#if EditorComp}
250
- <input type="hidden" name={field.key} value={strVal} />
301
+ <input type="hidden" name={nativeFieldName} value={strVal} {disabled} />
251
302
  <EditorComp
252
- id={field.key}
303
+ id={controlId}
253
304
  value={strVal}
254
305
  placeholder={i18n.t('field.enterValue', { label: field.label })}
255
306
  preset="full"
256
- onchange={(html: string) => onchange(html)}
307
+ onchange={(html: string) => { if (!disabled) onchange(html); }}
308
+ aria-labelledby={labelId}
309
+ aria-required={field.required || undefined}
257
310
  aria-invalid={invalid || undefined}
258
- aria-describedby={errorId}
311
+ aria-describedby={describedBy}
259
312
  {disabled}
260
313
  />
261
314
  {:else}
262
315
  <Textarea
263
- id={field.key}
264
- name={field.key}
316
+ id={controlId}
317
+ name={nativeFieldName}
265
318
  value={strVal}
266
319
  oninput={(e) => onchange((e.target as HTMLTextAreaElement).value)}
267
320
  required={field.required}
268
321
  aria-invalid={invalid || undefined}
269
- aria-describedby={errorId}
322
+ aria-describedby={describedBy}
270
323
  {disabled}
271
324
  rows={10}
272
325
  placeholder={i18n.t('field.enterValue', { label: field.label })}
@@ -276,13 +329,13 @@
276
329
 
277
330
  {:else if field.type === 'textarea'}
278
331
  <Textarea
279
- id={field.key}
280
- name={field.key}
332
+ id={controlId}
333
+ name={nativeFieldName}
281
334
  value={strVal}
282
335
  oninput={(e) => onchange((e.target as HTMLTextAreaElement).value)}
283
336
  required={field.required}
284
337
  aria-invalid={invalid || undefined}
285
- aria-describedby={errorId}
338
+ aria-describedby={describedBy}
286
339
  {disabled}
287
340
  rows={4}
288
341
  placeholder={i18n.t('field.enterValue', { label: field.label })}
@@ -290,7 +343,18 @@
290
343
  />
291
344
 
292
345
  {:else if field.type === 'tree-select' || field.type === 'treeselect'}
346
+ {#if field.multiple}
347
+ {#each multiVal as selectedValue, _i (selectedValue)}
348
+ <input type="hidden" name={nativeArrayName} value={String(selectedValue)} {disabled} />
349
+ {/each}
350
+ {:else}
351
+ <input type="hidden" name={nativeFieldName} value={value == null ? '' : String(value)} {disabled} />
352
+ {/if}
293
353
  <TreeSelect
354
+ id={controlId}
355
+ ariaLabel={field.label}
356
+ {...definedOptions({ describedby: describedBy })}
357
+ invalid={invalid}
294
358
  options={(field.treeOptions ?? field.options ?? []) as TreeSelectOption[]}
295
359
  {...definedOptions({ "value": value as string | number | (string | number)[] | undefined })}
296
360
  multiple={field.multiple}
@@ -300,7 +364,12 @@
300
364
  />
301
365
 
302
366
  {:else if field.type === 'cascader'}
367
+ <input type="hidden" name={nativeFieldName} value={JSON.stringify(Array.isArray(value) ? value : [])} {disabled} />
303
368
  <Cascader
369
+ id={controlId}
370
+ ariaLabel={field.label}
371
+ {...definedOptions({ describedby: describedBy })}
372
+ invalid={invalid}
304
373
  options={(field.cascaderOptions ?? field.options ?? []) as CascaderOption[]}
305
374
  value={value as (string | number)[]}
306
375
  separator={field.separator}
@@ -311,7 +380,14 @@
311
380
  />
312
381
 
313
382
  {:else if field.type === 'transfer'}
383
+ {#each multiVal as selectedValue, _i (selectedValue)}
384
+ <input type="hidden" name={nativeArrayName} value={String(selectedValue)} {disabled} />
385
+ {/each}
314
386
  <Transfer
387
+ id={controlId}
388
+ labelledby={labelId}
389
+ {...definedOptions({ describedby: describedBy })}
390
+ invalid={invalid}
315
391
  dataSource={(field.transferData ?? field.options?.map((o: { label: string; value: string | number }) => ({ key: o.value, title: o.label })) ?? []) as TransferItem[]}
316
392
  targetKeys={(value as (string | number)[]) ?? []}
317
393
  disabled={disabled}
@@ -321,13 +397,13 @@
321
397
  {:else if field.type === 'relation' && field.resource}
322
398
  {#if field.multiple}
323
399
  {#each multiVal as selectedValue, _i (selectedValue)}
324
- <input type="hidden" name={field.key} value={String(selectedValue)} />
400
+ <input type="hidden" name={nativeFieldName} value={String(selectedValue)} {disabled} />
325
401
  {/each}
326
402
  {:else}
327
- <input type="hidden" name={field.key} value={value == null ? '' : String(value)} />
403
+ <input type="hidden" name={nativeFieldName} value={value == null ? '' : String(value)} {disabled} />
328
404
  {/if}
329
405
  <ComboboxField
330
- id={field.key}
406
+ id={controlId}
331
407
  resource={field.resource}
332
408
  value={field.multiple ? multiVal : value as string | number | null}
333
409
  onchange={(v) => onchange(v)}
@@ -336,22 +412,22 @@
336
412
  multiple={field.multiple}
337
413
  placeholder={i18n.t('field.selectPlaceholder')}
338
414
  aria-invalid={invalid || undefined}
339
- aria-describedby={errorId}
415
+ aria-describedby={describedBy}
340
416
  {disabled}
341
417
  />
342
418
 
343
419
  {:else if field.type === 'select'}
344
- <input type="hidden" name={field.key} value={value == null ? '' : String(value)} {disabled} />
420
+ <input type="hidden" name={nativeFieldName} value={value == null ? '' : String(value)} {disabled} />
345
421
  {#if (field.options?.length ?? 0) > 8}
346
422
  <select
347
- id={field.key}
423
+ id={controlId}
348
424
  data-slot="select"
349
425
  class="svadmin-u-60fbb7713999 svadmin-u-426b8b75185b svadmin-u-6da6a3c3f741 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b svadmin-u-fc7473ca09eb svadmin-u-582e6ef4b245 svadmin-u-9c24ab70af61 svadmin-u-55d048ebfb1c svadmin-u-608dd26cd5ba svadmin-u-80b9d0ae125f svadmin-u-6b22a22a9752 svadmin-u-5f533b3a7de7 svadmin-u-b29d8adbad2e"
350
426
  value={selectToken}
351
427
  onchange={changeSelect}
352
428
  required={field.required}
353
429
  aria-invalid={invalid || undefined}
354
- aria-describedby={errorId}
430
+ aria-describedby={describedBy}
355
431
  disabled={disabled}
356
432
  >
357
433
  <option value="">{i18n.t('field.selectPlaceholder')}</option>
@@ -361,12 +437,12 @@
361
437
  </select>
362
438
  {:else}
363
439
  <Select
364
- id={field.key}
440
+ id={controlId}
365
441
  value={selectToken}
366
442
  onchange={changeSelect}
367
443
  required={field.required}
368
444
  aria-invalid={invalid || undefined}
369
- aria-describedby={errorId}
445
+ aria-describedby={describedBy}
370
446
  {disabled}
371
447
  >
372
448
  <option value="">{i18n.t('field.selectPlaceholder')}</option>
@@ -378,23 +454,23 @@
378
454
 
379
455
  {:else if field.type === 'multiselect'}
380
456
  {#each multiVal as selectedValue, selectedIndex (`${selectedValue}-${selectedIndex}`)}
381
- <input type="hidden" name={`${field.key}[]`} value={String(selectedValue)} {disabled} />
457
+ <input type="hidden" name={nativeArrayName} value={String(selectedValue)} {disabled} />
382
458
  {/each}
383
459
  <div
384
460
  class="svadmin-u-6f7e013d6499 svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-eb6e8b881acd svadmin-u-558f64349245 svadmin-u-92bf82f493b1"
385
461
  role="group"
386
- aria-labelledby="label-{field.key}"
387
- aria-describedby={errorId}
462
+ aria-labelledby={labelId}
463
+ aria-describedby={describedBy}
388
464
  data-invalid={invalid || undefined}
389
465
  >
390
466
  {#each field.options ?? [] as opt, _i (_i)}
391
467
  <label class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-fc7473ca09eb svadmin-u-34516836730d svadmin-u-39f703dbe296 svadmin-u-07389a777c1f svadmin-u-d8e0e382c67b svadmin-u-465609a240a8 svadmin-u-ceb69a6b0e5f">
392
468
  <Checkbox
393
- id={`${field.key}-option-${_i}`}
469
+ id={`${controlId}-option-${_i}`}
394
470
  checked={multiVal.includes(opt.value)}
395
471
  onCheckedChange={() => toggleMulti(opt.value)}
396
472
  aria-invalid={invalid || undefined}
397
- aria-describedby={errorId}
473
+ aria-describedby={describedBy}
398
474
  disabled={disabled || opt.disabled}
399
475
  />
400
476
  {opt.label}
@@ -417,14 +493,14 @@
417
493
  {/if}
418
494
 
419
495
  {:else if field.type === 'boolean'}
420
- <input type="hidden" name={field.key} value={String(boolVal)} />
496
+ <input type="hidden" name={nativeFieldName} value={String(boolVal)} {disabled} />
421
497
  <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-6b7d6e21ccbd">
422
498
  <Switch
423
- id={field.key}
499
+ id={controlId}
424
500
  checked={boolVal}
425
501
  onCheckedChange={(v) => onchange(v)}
426
502
  aria-invalid={invalid || undefined}
427
- aria-describedby={errorId}
503
+ aria-describedby={describedBy}
428
504
  disabled={disabled}
429
505
  />
430
506
  <span class="svadmin-u-fc7473ca09eb svadmin-u-bfa603190748">{boolVal ? i18n.t('common.yes') : i18n.t('common.no')}</span>
@@ -432,21 +508,20 @@
432
508
 
433
509
  {:else if field.type === 'tags'}
434
510
  <TagsInput
435
- name={`${field.key}[]`}
436
511
  value={tagsVal}
437
512
  onchange={onchange}
438
513
  placeholder={i18n.t('field.tagsPlaceholder')}
439
514
  required={field.required}
440
515
  invalid={invalid}
441
- describedby={errorId}
442
516
  ariaLabel={field.label}
443
517
  {disabled}
518
+ {...definedOptions({ name: nativeArrayName, describedby: describedBy })}
444
519
  />
445
520
 
446
521
  {:else if field.type === 'date' || field.type === 'time' || field.type === 'datetime'}
447
522
  <DateTimeInput
448
- id={field.key}
449
- name={field.key}
523
+ id={controlId}
524
+ name={nativeFieldName}
450
525
  mode={field.type}
451
526
  min={field.dateInput?.min}
452
527
  max={field.dateInput?.max}
@@ -459,13 +534,13 @@
459
534
  onchange={(next) => onchange(next)}
460
535
  required={field.required}
461
536
  invalid={invalid}
462
- describedby={errorId}
537
+ describedby={describedBy}
463
538
  {disabled}
464
539
  />
465
540
 
466
541
  {:else if field.type === 'daterange'}
467
542
  <DateRangeInput
468
- startId={`${field.key}-start`}
543
+ startId={`${controlId}-start`}
469
544
  mode={field.dateInput?.rangeMode ?? 'date'}
470
545
  valueMode={field.dateInput?.valueMode}
471
546
  timeZone={field.dateInput?.timeZone}
@@ -475,78 +550,116 @@
475
550
  step={field.dateInput?.step}
476
551
  disabledDate={field.dateInput?.disabledDate}
477
552
  presets={field.dateInput?.presets}
478
- endId={`${field.key}-end`}
553
+ endId={`${controlId}-end`}
479
554
  startAriaLabel={`${field.label} ${i18n.t('common.startDate')}`}
480
555
  endAriaLabel={`${field.label} ${i18n.t('common.endDate')}`}
481
- startName={`${field.key}.start`}
482
- endName={`${field.key}.end`}
483
556
  value={value as DateRangeInputValue | null}
484
557
  onchange={(next) => onchange(next)}
485
558
  required={field.required}
486
559
  invalid={invalid}
487
- describedby={errorId}
488
560
  {disabled}
561
+ {...definedOptions({
562
+ startName: nativeSubmit ? `${field.key}.start` : undefined,
563
+ endName: nativeSubmit ? `${field.key}.end` : undefined,
564
+ describedby: describedBy,
565
+ })}
489
566
  />
490
567
 
491
568
  {:else if field.type === 'rate' || field.type === 'rating'}
492
569
  <Rate
493
- name={field.key}
570
+ name={nativeFieldName}
494
571
  value={numVal ?? 0}
495
572
  onchange={onchange}
496
573
  required={field.required}
497
574
  invalid={invalid}
498
- describedby={errorId}
499
575
  ariaLabel={field.label}
500
576
  {disabled}
577
+ {...definedOptions({ describedby: describedBy })}
501
578
  />
502
579
 
503
580
  {:else if field.type === 'file'}
504
- <FileUpload
505
- id={field.key}
506
- name={field.key}
507
- multiple={field.multiple}
508
- required={field.required && !value}
509
- disabled={disabled}
510
- accept={field.accept}
511
- maxSize={field.maxSize}
512
- onChange={(items) => onchange(field.multiple ? items.map(item => item.file) : items[0]?.file ?? null)}
513
- />
581
+ {#if fileReferenceMode || upload}
582
+ <!-- AutoForm 显式 JSON 文件模式:只写入可序列化引用。 -->
583
+ {#key fileScopeKey}
584
+ <FileUploadField
585
+ id={controlId}
586
+ {field}
587
+ {value}
588
+ {onchange}
589
+ {invalid}
590
+ {...definedOptions({ describedby: describedBy })}
591
+ {...definedOptions({ disabled, upload, cancelUpload, onUploadStateChange })}
592
+ />
593
+ {/key}
594
+ {:else}
595
+ <!-- 独立 FieldRenderer 保留原生 multipart 字段名与 File 值。 -->
596
+ <FileUpload
597
+ id={controlId}
598
+ name={field.key}
599
+ multiple={field.multiple}
600
+ required={field.required && !value}
601
+ disabled={disabled}
602
+ ariaLabel={field.label}
603
+ invalid={invalid}
604
+ {...definedOptions({ describedby: describedBy })}
605
+ accept={field.accept}
606
+ maxSize={field.maxSize}
607
+ onChange={(items) => onchange(field.multiple ? items.map(item => item.file) : items[0]?.file ?? null)}
608
+ />
609
+ {/if}
514
610
 
515
611
  {:else if field.type === 'images'}
516
612
  {#each imagesVal as url, imageIndex (imageIndex)}
517
- <input type="hidden" name={`${field.key}[]`} value={url} />
613
+ <input type="hidden" name={nativeArrayName} value={url} {disabled} />
518
614
  {/each}
519
615
  <div class="svadmin-u-6f7e013d6499">
520
616
  {#each imagesVal as url, i (i)}
521
617
  <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
522
618
  <Input
523
- id={i === 0 ? field.key : undefined}
619
+ id={i === 0 ? controlId : undefined}
524
620
  type="text"
525
621
  value={url}
526
622
  oninput={(e) => updateImage(i, (e.target as HTMLInputElement).value)}
527
623
  placeholder="https://example.com/image.jpg"
528
624
  class="svadmin-u-36e579c0b41c"
529
625
  aria-invalid={invalid || undefined}
530
- aria-describedby={errorId}
626
+ aria-describedby={describedBy}
531
627
  {disabled}
532
628
  />
533
629
  {#if url}
534
630
  <div class="svadmin-u-665f07fe73cc svadmin-u-012fbd121f37"><MediaThumbnail src={url} alt="preview" size="full" fit="cover" showOverlay={false} /></div>
535
631
  {/if}
536
- <TooltipButton tooltip={i18n.t('common.removeImage')} variant="ghost" size="icon" class="svadmin-u-ed8a5df7b2fb svadmin-u-2bbcfc3b5179 svadmin-u-012fbd121f37" onclick={() => removeImage(i)}>
632
+ <TooltipButton
633
+ tooltip={i18n.t('common.removeImage')}
634
+ variant="ghost"
635
+ size="icon"
636
+ class="svadmin-u-ed8a5df7b2fb svadmin-u-2bbcfc3b5179 svadmin-u-012fbd121f37"
637
+ aria-describedby={describedBy}
638
+ aria-invalid={invalid || undefined}
639
+ {disabled}
640
+ onclick={() => removeImage(i)}
641
+ >
537
642
  <X class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
538
643
  </TooltipButton>
539
644
  </div>
540
645
  {/each}
541
- <Button variant="outline" size="sm" type="button" onclick={addImage}>
646
+ <Button
647
+ variant="outline"
648
+ size="sm"
649
+ type="button"
650
+ aria-describedby={describedBy}
651
+ aria-invalid={invalid || undefined}
652
+ {disabled}
653
+ onclick={addImage}
654
+ >
542
655
  <Plus class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-618162408e7a" /> {i18n.t('field.addImage')}
543
656
  </Button>
544
657
  </div>
545
658
 
546
659
  {:else if field.type === 'json'}
547
660
  <Textarea
548
- id={field.key}
549
- name={field.key}
661
+ id={controlId}
662
+ name={nativeFieldName}
550
663
  value={jsonEditText}
551
664
  onfocus={() => { jsonEditing = true; }}
552
665
  onblur={() => {
@@ -564,20 +677,20 @@
564
677
  }}
565
678
  {disabled}
566
679
  aria-invalid={invalid || undefined}
567
- aria-describedby={errorId}
680
+ aria-describedby={describedBy}
568
681
  rows={6}
569
682
  class="svadmin-u-5bd7b080992c svadmin-u-0e65706bcccd svadmin-u-359090c2d529"
570
683
  />
571
684
 
572
685
  {:else}
573
686
  <Input
574
- id={field.key}
575
- name={field.key}
687
+ id={controlId}
688
+ name={nativeFieldName}
576
689
  type="text"
577
690
  value={strVal}
578
691
  oninput={(e) => onchange((e.target as HTMLInputElement).value)}
579
692
  aria-invalid={invalid || undefined}
580
- aria-describedby={errorId}
693
+ aria-describedby={describedBy}
581
694
  {disabled}
582
695
  />
583
696
  {/if}